I have a obj model that I'm loading via THREE.LoadingManager and a few textures. When I'm opening the page with clear cache - I'm always getting model without textures. I can see them after pressing F5. My code looks like this:
var manager = new THREE.LoadingManager();
var loader = new THREE.TextureLoader(manager);
var textures_loaded = 0;
var id;
for (id in materials) {
loader.load('images/' + materials[id].unique_id + '.jpg', function(t) {
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.LinearMipMapLinearFilter;
var re = /images\/(\d+)\.jpg/g;
var result = re.exec(t.image.currentSrc);
material = materials.filter(function(obj) {
return obj.unique_id == result[1];
}).shift();
material.setTexture(t);
textures_loaded += 1;
if (textures_loaded == materials.length) {
mainLoop();
}
});
}
var loader = new THREE.OBJLoader(manager);
loader.load('obj/' + model.model_name, function (object) {
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
var mesh = model.meshes.filter(function(mesh) {
return mesh.name == child.name;
}).shift();
child.material.map = mesh.material.texture;
child.geometry.buffersNeedUpdate;
child.geometry.uvsNeedUpdate;
}
});
scene.add(object);
}
);
I'm running mainLoop only after all textures are loaded. But sometimes model loads faster. How can I control order of loading objects using THREE.LoadingManager ?. How can I show model only after everything is loaded ?
I would do something like this. Have two functions responsible for loading each set of content inline. In this case the meshes will not be loaded until the materials are done loading.
var manager = new THREE.LoadingManager();
var loader = new THREE.TextureLoader(manager);
function loadMaterials(materials, callback){
var textures_loaded = 0;
for (var id in materials) {
loader.load('images/' + materials[id].unique_id + '.jpg', function(t) {
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.LinearMipMapLinearFilter;
var re = /images\/(\d+)\.jpg/g;
var result = re.exec(t.image.currentSrc);
material = materials.filter(function(obj) {
return obj.unique_id == result[1];
}).shift();
material.setTexture(t);
textures_loaded += 1;
if (textures_loaded == materials.length) {
callback("materialsLoaded");
//mainLoop();
}
});
}
}
var loadedMeshes = [];
function loadMeshes(meshes, callback){
var meshes_loaded = 0;
for(var i in meshes){
var loader = new THREE.OBJLoader(manager);
loader.load('obj/' + model.model_name, function (object) {
loadedMeshes.push(object);
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
//you'll need to map to the right material.
child.material.map = mesh.material.texture;
child.geometry.buffersNeedUpdate;
child.geometry.uvsNeedUpdate;
}
});
if(loadedMeshes.length == meshes.length){
callback("meshesLoaded");
}
};
}
}
var materials = ["material1", "material2"];
var meshes = ["mesh1.obj", "mesh2.obj"];
function onLoad(){
for(var i in loadedMeshes){
scene.add(loadedMeshes[i]);
}
mainLoop();
}
function init(){
loadMaterials(materials, function(t){
console.log(t);
loadMeshes(meshes, function(t){
console.log(t);
onLoad(t);
})
})
}
Maybe you can try to execute your mainLoop only when the document is ready, using some jQuery:
$('document').ready(function mainLoop(){});
"this event does not get triggered until all assets such as images have been completely received"
from jQuery .ready event
Related
I am trying to render textures on planes loaded from different URL's. For some reason after 2nd or 3rd image I can see in browser that loading image is stuck and it is not being rendered.
Adding the code used:
function init() {
loadPicturesFromDirectUrl();
}
function loadPicturesFromDirectUrl(currentPictureIndex) {
if (currentPictureIndex === undefined) {
currentPictureIndex = 0;
}
var picture = data.pictures[currentPictureIndex];
var loader = new THREE.TextureLoader();
loader.load(picture.url, function (texture) {
renderPicture(picture, texture);
currentPictureIndex++;
if (currentPictureIndex > data.pictures.length - 1) {
return;
}
loadPicturesFromDirectUrl(currentPictureIndex);
}, null, function (e) {
console.log(e);
});
}
function renderPicture(picture, texture) {
var planeGeometry = new THREE.PlaneGeometry(picture.size.width, picture.size.height);
var planeMaterial = new THREE.MeshBasicMaterial({ map: texture });
var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.position.x = picture.location.x;
planeMesh.position.y = picture.location.y;
planeMesh.rotateY(myzeum.toRadians(180));
scene.add(planeMesh);
}
You may want to try to preload all the pictures in one function and store them in an array and render them only after all of them have been loaded. Like so:
var all_textures = [];
function init() {
loadPicturesFromDirectUrl();
for int (i = 0; i < all_picture.length; i++) {
renderPicture(data.pictures[i], all_textures[i])
}
}
function loadPicturesFromDirectUrl(currentPictureIndex) {
if (currentPictureIndex === undefined) {
currentPictureIndex = 0;
}
var picture = data.pictures[currentPictureIndex];
var loader = new THREE.TextureLoader();
loader.load(picture.url, function (texture) {
all_textures[currentPictureIndex] = texture
currentPictureIndex++;
if (currentPictureIndex > data.pictures.length - 1) {
return;
}
loadPicturesFromDirectUrl(currentPictureIndex);
}, null, function (e) {
console.log(e);
});
}
function renderPicture(picture, texture) {
var planeGeometry = new THREE.PlaneGeometry(picture.size.width, picture.size.height);
var planeMaterial = new THREE.MeshBasicMaterial({ map: texture });
var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.position.x = picture.location.x;
planeMesh.position.y = picture.location.y;
planeMesh.rotateY(myzeum.toRadians(180));
scene.add(planeMesh);
}
Using the many examples on ThreeJS, I'm able to load multiple OBJ files into my scene. I'm also able to load multiple images as textures. However, the textures get assigned to the objects in 'order of appearance' and therefore, sometimes the wrong image gets assigned to the OBJ files.
I retrieve a list of OBJ files and textures from a JavaScript array, let's say:
…
"arr_threejs": [{
"geometry": "my_first_object.obj",
"material_uvmap": "my_first_texture.jpg",
}, {
"geometry": "my_second_object.obj",
"material_uvmap": "my_second_object.obj",
}],…
Then, I use this 'loader' class to load the textures:
for (var i = 0; i < arr_threejs.length; i++) {
var loader = new THREE.ImageLoader(manager);
str_material_uvmap_url = arr_threejs[i].material_uvmap;
loader.load(str_material_uvmap_url, function (image) {
console.log(image);
var index = textures.push(new THREE.Texture()) - 1;
textures[index].image = image;
textures[index].needsUpdate = true;
});
}
And, simillary, the geometry:
var loader = new THREE.OBJLoader(manager);
for (var i = 0; i < arr_threejs.length; i++) {
str_model_url = arr_threejs[i].geometry;
loader.load(str_model_url, function (object, i) {
var index = get_index_by_url(str_model_url); //doesn't work
objects[index] = object;
objects[index].traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material.map = textures[index];
child.material.side = THREE.DoubleSide;
}
});
scene.add(objects[index]);
}, onProgress, onError);
}
It seems I have no way in the callback function to know what object or what texture I'm dealing with. var index = get_index_by_url(str_model_url); doesnt work, because str_model_url isn't passed as arguement.
So, my question is,
specifically:
Is there a way to know the index of the image or object that I'm trying to load?
or generally:
Is there a standard way to load multple OBJ-files with textures?
You can use anonymus functions. For example:
loader.load(str_model_url, (function (url,scale) { return function (object, i) {
console.log( url );
console.log( scale );
}}) (str_model_url, 0.5)
, onProgress
, onError
);
Here's #stdob answer implemented in my code:
// MODEL
// model - LOAD TEXTURE
for (var i = 0; i < arr_threejs.length; i++) {
var loader = new THREE.ImageLoader(manager);
str_material_uvmap_url = arr_threejs[i].material_uvmap;
loader.load(str_material_uvmap_url, (function (url, index) {
return function (image, i) {
textures[index] = new THREE.Texture();
textures[index].image = image;
textures[index].needsUpdate = true;
}
})(str_material_uvmap_url, i)
, onProgress
, onError
);
}
// model - LOAD OBJECT
var loader = new THREE.OBJLoader(manager);
for (var i = 0; i < arr_threejs.length; i++) {
str_model_url = arr_threejs[i].geometry;
loader.load(str_model_url, (function (url, index) {
return function (object, i) {
objects[index] = object;
objects[index].traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material.map = textures[index];
child.material.side = THREE.DoubleSide;
}
});
scene.add(objects[index]);
}
})(str_model_url, i)
, onProgress
, onError
);
}
Using Three.js r68+ I have been needing to very slightly modify the source to get my animations working correctly. Unmodified only one of each type of model are animated (there are multiple spawns of each type of model).
This is the modified source at line 29407 (posted code beginning at 29389):
THREE.AnimationHandler = {
LINEAR: 0,
CATMULLROM: 1,
CATMULLROM_FORWARD: 2,
//
add: function () { console.warn( 'THREE.AnimationHandler.add() has been deprecated.' ); },
get: function () { console.warn( 'THREE.AnimationHandler.get() has been deprecated.' ); },
remove: function () { console.warn( 'THREE.AnimationHandler.remove() has been deprecated.' ); },
//
animations: [],
init: function ( data ) {
// original -> if ( data.initialized === true ) return;
if ( data.initialized === true ) return data; //<-- modified
The function now returns the animation data if initialized. I'm assuming it doesn't do so because of caching. My question is what is the best practice for animating multiple models that have the same animation names? I tried naming them uniquely per model name ie: "Death_MaleWarrior" but this had no effect.
Currently my models and animations are handled like so:
var modelArray = [];
var geoCache = [];
var loader = new THREE.JSONLoader(true);
var _MODEL = function(data){
this.data = data;
this.mesh = null;
this.animations = {};
this.canAnimate = false;
this.parseAnimations = function () {
var len,i,anim;
if (this.mesh) {
if( this.mesh.geometry.animations ){
this.canAnimate = true;
len = this.mesh.geometry.animations.length;
if( len ){
for(i=0;i<len;i++){
anim = this.mesh.geometry.animations[i];
if( anim ){
this.animations[anim.name] = new THREE.Animation( this.mesh, anim );
}
}
}
}
}
};
this.playAnimation = function(label){
if (this.canAnimate) {
if( this.animations[label] ){
//if( this.animations[label].data ){
this.animations[label].play(0,1);
//}
}
}
return false;
};
this.load = function(geo){
var mat;
mat = new THREE.MeshPhongMaterial({color:somecolor, skinning:true})
this.mesh = new THREE.SkinnedMesh(geo,mat);
this.mesh.position.x = this.data.position[0];
this.mesh.position.y = this.data.position[1];
this.mesh.position.z = this.data.position[2];
this.parseAnimations();
scene.add(this.mesh);
this.playAnimation('Idle');
};
this.init = function(){
var geo;
if( geoCache[this.data.name] ){
geo = geoCache[this.data.name];
this.load(geo);
}else{
geo = loader.parse(JSON.parse(this.data.json)).geometry;
geoCache[this.data.name] = geo;
this.load(geo);
}
};
this.init();
};
var dataArray = [{name:'MaleWarrior',json:'json_data',position:[x,y,z]},{name:'FemaleWarrior',json:'json_data',position:[x,y,z]},{name:'MaleWarrior',json:'json_data',position:[x,y,z]}];
for(var i=0, len=objectArray.length; i<len; i++){
modelArray.push(new _MODEL(dataArray[i]) );
}
In this example the first MaleWarrior will animate but the second will not. If there was a second female she would not be animated either as the animation (even though it is a new THREE.Animation() ) will be considered initialized and will not return any data. If I do not check for the existence of animations.data in playAnimation I get the error ""Uncaught TypeError: Cannot read property 'name' of undefined " on line 29665".
Is what I'm doing bypassing animation caching and hurting performance? I feel like I'm missing something. How will an animation play without data?
All animation names are the same for every model "Idle", "Run", "Attack" etc.
Any help would be appreciated. If I'm not being clear enough please let me know. I have added additional detail.
This turned out to be an official three.js bug.
https://github.com/mrdoob/three.js/issues/5516
I need to upload attached resized images into a couchdb doc. Right now I'm not resizing images, only uploading them by using the following code:
function attachFile(event) {
event.preventDefault();
var form_data = {};
$("form.attach-file :file").each(function() {
form_data[this.name] = this.value;
});
if (!form_data._attachments || form_data._attachments.length == 0) {
alert("Please select a file to upload.");
return;
}
var id = $("#ant-show").data("doc")._id;
$(this).ajaxSubmit({
url: "db/" + $.couch.encodeDocId(id),
success: function(resp) {
$('#modal-attach').modal("hide");
helios_link(id);
}
});
}
The code I'm using to rezise images, but that doesn't work to upload them, is the following:
function attachFile(event) {
function isImage (str) {
return str.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i);
}
function resizeAndUpload(file, callback, progress)
{
var reader = new FileReader();
reader.onloadend = function() {
var tempImg = new Image();
tempImg.onload = function() {
var MAX_WIDTH = 500;
var MAX_HEIGHT = 500;
var tempW = tempImg.width;
var tempH = tempImg.height;
if (tempW > tempH) {
if (tempW > MAX_WIDTH) {
tempH *= MAX_WIDTH / tempW;
tempW = MAX_WIDTH;
}
} else {
if (tempH > MAX_HEIGHT) {
tempW *= MAX_HEIGHT / tempH;
tempH = MAX_HEIGHT;
}
}
var resizedCanvas = document.createElement('canvas');
resizedCanvas.width = tempW;
resizedCanvas.height = tempH;
var ctx = resizedCanvas.getContext("2d");
ctx.drawImage(this, 0, 0, tempW, tempH);
var dataURL = resizedCanvas.toDataURL("image/jpeg");
var file = dataURLtoBlob(dataURL);
var fd = $("#upload");
fd.append("_attachments", file);
var id = $("#ant-show").data("doc")._id;
console.log(fd);
fd.ajaxSubmit({
url: "db/" + $.couch.encodeDocId(id),
success: function(resp) {
$('#modal-attach').modal("hide");
helios_link(id);
}
});
};
tempImg.src = reader.result;
}
reader.readAsDataURL(file);
}
function dataURLtoBlob(dataURL) {
// Decodifica dataURL
var binary = atob(dataURL.split(',')[1]);
// Se transfiere a un array de 8-bit unsigned
var array = [];
var length = binary.length;
for(var i = 0; i < length; i++) {
array.push(binary.charCodeAt(i));
}
// Retorna el objeto Blob
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
function uploaded(response) {
// Código siguiente a la subida
}
function progressBar(percent) {
// Código durante la subida
}
event.preventDefault();
console.clear();
var files = document.getElementById('_attachments').files;
console.log(files);
resizeAndUpload(files[0], uploaded, progressBar);
}
Do anybody know how can I improve my code to make it work? I would like to have in fact two different solutions, one that helps me to improve my code and the second one, to get instructions on how to upload a URL BLOB as attachments into a couchdb document.
i'm having some problems migrating my code to the newest release of Three.JS.
I've already tried alot, but my models keep looking black (the color function seems broken)) and as soon as I try loading a model with a texture, it crashes.
I know some things have changed regarding textures in the transition from r52 to r53 but I can't seem to implement it right. I use a dynamic loading system to cache JSON models.
Currently the code looks like this:
var MB = MB || {};
MB.Part = function () {
this.id = MB.ObjectCount++;
this.type = 'part';
this.name = '';
this.reference = undefined;
this.mesh = undefined;
this.colour = undefined;
this.texture = undefined;
this.Object3D = undefined;
this.parent = undefined;
this.visible = true;
this.position = new THREE.Vector3();
this.rotation = new THREE.Vector3();
MB.Objects[this.id] = this
};
MB.Part.prototype.load = function (reference, name, mesh, color, texture, position, rotation, visible, visible3D, callback) {
var context = this;
if (!position) position = new THREE.Vector3();
if (!rotation) rotation = new THREE.Vector3();
if (this.isGeometrySetup(mesh, texture)) {
this.insert(context, reference, name, mesh, color, texture, position, rotation, visible, visible3D);
if (callback) callback()
} else {
var loader = new THREE.JSONLoader(false);
loader.load(PATH + 'data/parts/json/' + mesh + '.js', function (geometry) {
context.setupGeometry(geometry, mesh, texture);
context.insert(context, reference, name, mesh, color, texture, position, rotation, visible, visible3D);
if (callback) callback()
})
}
};
MB.Part.prototype.insert = function (context, reference, name, mesh, color, texture, position, rotation, visible, visible3D) {
context.reference = reference;
context.mesh = mesh;
context.name = name;
if (color) context.colour = color;
if (texture) context.texture = texture;
context.visible = visible;
if (color) {
context.setupColour(color);
var Object3D = new THREE.Mesh(MB.Library.geometries[mesh], MB.Library.colours[color])
} else if (texture) {
var Object3D = new THREE.Mesh(MB.Library.geometries[texture], new THREE.MeshFaceMaterial())
}
context.Object3D = Object3D;
context.Object3D.doubleSided = true;
context.position.copy(position);
context.rotation.copy(rotation);
Object3D.Object = context;
Object3D.scale.set(SCALE, SCALE, SCALE);
Object3D.position.copy(context.position);
Object3D.rotation.copy(context.rotation);
Object3D.visible = visible3D;
MB.Object3Ds[Object3D.id] = Object3D
};
MB.Part.prototype.isGeometrySetup = function (mesh, texture) {
if (texture) {
if (!MB.Library.geometries[texture]) return false;
else return true
} else {
if (!MB.Library.geometries[mesh]) return false;
else return true
}
};
MB.Part.prototype.setupGeometry = function (geometry, mesh, texture) {
if (texture) {
if (!MB.Library.geometries[texture]) {
var file = this.loadTexture(PATH + 'data/parts/textures/' + texture + '.png');
MB.Library.geometries[texture] = geometry;
MB.Library.geometries[texture].materials[0].map = file;
MB.Library.geometries[texture].materials[0].wireframe = (RENDER === 'WIREFRAME') ? true : false
}
} else {
if (!MB.Library.geometries[mesh]) {
MB.Library.geometries[mesh] = geometry
}
}
};
MB.Part.prototype.loadTexture = function (url, mapping, callback) {
var image = new Image(),
texture = new THREE.Texture(image, mapping);
image.onload = function () {
texture.needsUpdate = true;
if (callback) callback(this)
};
image.crossOrigin = '';
image.src = url;
return texture
};
MB.Part.prototype.setupColour = function (colour) {
if (!MB.Library.colours[colour]) {
var materialColour = '0x' + COLOURS[colour].hex;
var materialOpacity = COLOURS[colour].opacity;
MB.Library.colours[colour] = new THREE.MeshPhongMaterial({
ambient: new THREE.Color(materialColour),
color: new THREE.Color(materialColour),
opacity: materialOpacity,
shininess: 100
});
MB.Library.colours[colour].wireframe = (RENDER === 'WIREFRAME') ? true : false;
MB.Library.colours[colour].transparent = (COLOURS[colour].opacity < 1) ? true : false
}
};
MB.Part.prototype.setColour = function (colour) {
if (this.colour) {
this.setupColour(colour);
this.colour = colour;
this.Object3D.material = MB.Library.colours[colour]
}
};
MB.ObjectCount = 0;
MB.Object3Ds = {};
MB.Objects = {};
MB.Library = {
'colours': {},
'geometries': {}
};
The problem seems to be situated here:
if (texture) {
if (!MB.Library.geometries[texture]) {
var file = this.loadTexture(PATH + 'data/parts/textures/' + texture + '.png');
MB.Library.geometries[texture] = geometry;
MB.Library.geometries[texture].materials[0].map = file;
MB.Library.geometries[texture].materials[0].wireframe = (RENDER === 'WIREFRAME') ? true : false
}
and in the overall way I still handle textures.
Any help would be greatly appreciated!
Thomas
Geometry doesn't have a materials array anymore. The array has been moved to MeshFaceMaterial.
new THREE.Mesh( geometry, new THREE.MeshFaceMaterials( materials ) );