ThreeJS Upload multiple textures for .obj - javascript

So I'm trying to upload a .obj file that also has multiple .png and .jpg files that are it's textures. The problem is I'm not sure how to even handle all these textures when they are uploaded.
Heres my code so far:
var loader = new THREE.OBJLoader(manager);
loader.load(obj_path, function (obj) {
model = obj;
modelWithTextures = true;
model.traverse( function ( child ) {
if ( child.isMesh ) child.material.map = texture;
} );
var textureLoader = new THREE.TextureLoader( manager );
var i;
for (var i = 0; i < files.length; i++) {
file = files[i];
console.log('Texture Files:' + files[i].name);
var texture = textureLoader.load(files[i].name);
}
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
setCamera(model);
setSmooth(model);
model.position.set(0, 0, 0);
setBoundBox(model);
setPolarGrid(model);
setGrid(model);
setAxis(model);
scaleUp(model);
scaleDown(model);
fixRotation(model);
resetRotation(model);
selectedObject = model;
outlinePass.selectedObjects = [selectedObject];
outlinePass.enabled = false;
renderer.render( scene, camera );
scene.add(model);
});
As you can see I'm using the textureLoader but just not sure what to do.
I'm very new to threeJS and 3d models.
Any help or advice would be much appreciated.
Thank you.

In your code, you probably won't see very much, because you render the scene before you add the model to the scene. You should render the scene after you did any changes (unless you have a render loop with requestAnimationFrame). Loading textures also is asynchronous. Use the callback of the .load method to better react when things are finished loading, e.g. trigger render().
I would load the textures within model.traverse() callback. But you have to determine which texture belongs to which mesh. This depends on how your data is structured.
var files = ['mesh1_tex.jpg', 'mesh2_tex.jpg'];
var loader = new THREE.OBJLoader( manager );
var textureLoader = new THREE.TextureLoader( manager );
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 0, -100); // depends on the size and location of your loaded model
var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
var scene = new THREE.Scene();
loader.load(obj_path, function ( model ) {
model.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
// here you have to find a way how to choose the correct texture for the mesh
// the following is just an example, how it could be done, but it depends on your data
const file = files.find( f => f === child.name + '_tex.jpg');
if (!file) {
console.warn(`Couldn't find texture file.`);
return;
}
textureLoader.load(file, ( texture ) => {
child.material.map = texture;
child.material.needsupdate = true;
render(); // only if there is no render loop
});
}
} );
scene.add( model );
camera.lookAt( model ); // depends on the location of your loaded model
render(); // only if there is no render loop
});
function render() {
renderer.render( scene, camera );
}
Disclaimer: I just wrote down this code without testing.

Related

Access parts of a GLTF import in the main rendering loop (sorry if noobish)

I’m a Unity developer, trying to learn Three.js.
One the many problems I encounter may sound simple, but it’s a pain in the a** for me.
All I wanna do is to import and animate a 3D logo in my Three.js app.
This logo is made out of 4 different meshes (elem1 to elem4) which don’t overlap.
It was exported as a FBX, then converted to GLTF using an online converter.
No problem when importing it, resizing it and even changing its material.
My problem is : how to refer to the whole object, and also to its 4 elements, in order to animate them in my « animate » function (I mean in my main rendering loop) ?
The only thing I could do was to create a second « animate » function within the loader callback, which seems a bit weird to me.
I can’t find a way to refer to them in the main scope of my app.
Dumping my GLTF import gives this hierarchy (these are called « nodes », am I right ?) :
AuxScene [Scene]
*no-name* [Object3D]
elem1 [Mesh]
elem2 [Mesh]
elem3 [Mesh]
elem4 [Mesh]
Here’s my code, cleared from unnecessary stuff :
'use strict';
// CANVAS AND RENDERER
const canvas = document.querySelector('#myCanvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
// SCENE AND BACKGROUND
var scene = new THREE.Scene();
const loader = new THREE.TextureLoader();
scene.background = loader.load( 'images/background.jpg');
// CAMERA
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 100;
// MATERIALS
var material = new THREE.MeshNormalMaterial ();
// ---------------------------------- LOGO IMPORTATION
var gltfLoader = new THREE.GLTFLoader();
var root;
var elem1, elem2, elem3, elem4;
gltfLoader.load('gltf/logo.gltf', function(gltf) {
root = gltf.scene;
root.rotation.x = Math.PI / 2;
scene.add(root);
root.traverse( function( child ) {
if ( child instanceof THREE.Mesh ) { child.material = material; }
} );
elem1 = root.getObjectByName('element1');
elem2 = root.getObjectByName('element2');
elem3 = root.getObjectByName('element3');
elem4 = root.getObjectByName('element4');
console.log(dumpObject(root).join('\n'));
// logo animations
var speed = 0.0005;
var turnsBeforeStop = 4;
requestAnimationFrame( animate2 );
function animate2( time ) {
root.rotation.z = Math.sin (time * 0.0005) * 0.5;
root.rotation.x = Math.PI/3 + Math.sin(time * 0.0003) * 0.5;
if(elem1.rotation.y < Math.PI * turnsBeforeStop){
elem1.rotation.y = time * speed*2;
elem2.rotation.z = time * speed*2;
elem3.rotation.y = time * -speed;
elem4.rotation.z = time * -speed*2;
}
requestAnimationFrame( animate2 );
}
});
// ------------------------------------------------------------ END LOGO
renderer.render( scene, camera );
requestAnimationFrame( animate );
// ANIMATION MAIN LOOP
function animate( time ) {
/*
This is where I would like to access my logo (as a whole, and also its separate parts).
But root.rotation or elem1.rotation won't work here and give me this error :
TypeError: undefined is not an object (evaluating 'elem1.rotation')
*/
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
// OBJECT DUMPING
function dumpObject(obj, lines = [], isLast = true, prefix = ' ') {
const localPrefix = isLast ? '└─' : '├─';
lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
const newPrefix = prefix + (isLast ? ' ' : '│ ');
const lastNdx = obj.children.length - 1;
obj.children.forEach((child, ndx) => {
const isLast = ndx === lastNdx;
dumpObject(child, lines, isLast, newPrefix);
});
return lines;
}
Thanx for any help.
The problem is that you try to access root before a value (the glTF model) is assigned to it. Notice that GLTFLoader.load() is asynchronous. So the onLoad() callback which sets root is not immediately called but with some amount of delay.
There are several approaches to solve this issue. You can check in your animation loop if root is not undefined. It would look like so:
function animate( time ) {
requestAnimationFrame( animate );
if ( root ) {
// do something with root
}
renderer.render( scene, camera );
}
Or you start animating after onLoad() has finished. In this case, the code would look like so:
'use strict';
// CANVAS AND RENDERER
const canvas = document.querySelector('#myCanvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
// SCENE AND BACKGROUND
var scene = new THREE.Scene();
const loader = new THREE.TextureLoader();
scene.background = loader.load( 'images/background.jpg');
// CAMERA
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 100;
// MATERIALS
var material = new THREE.MeshNormalMaterial ();
// ---------------------------------- LOGO IMPORTATION
var gltfLoader = new THREE.GLTFLoader();
var root;
var elem1, elem2, elem3, elem4;
gltfLoader.load('gltf/logo.gltf', function(gltf) {
root = gltf.scene;
root.rotation.x = Math.PI / 2;
scene.add(root);
root.traverse( function( child ) {
if ( child instanceof THREE.Mesh ) { child.material = material; }
} );
animate(); // start animating
});
// ------------------------------------------------------------ END LOGO
// ANIMATION MAIN LOOP
function animate() {
requestAnimationFrame( animate );
// do something with root
renderer.render( scene, camera );
}
// OBJECT DUMPING
function dumpObject(obj, lines = [], isLast = true, prefix = ' ') {
const localPrefix = isLast ? '└─' : '├─';
lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
const newPrefix = prefix + (isLast ? ' ' : '│ ');
const lastNdx = obj.children.length - 1;
obj.children.forEach((child, ndx) => {
const isLast = ndx === lastNdx;
dumpObject(child, lines, isLast, newPrefix);
});
return lines;
}

three.js mtl loader renders black

I am trying to add a level .obj for my program but it renders black. The .mtl file requires several images placed everywhere (not one space is non-textured). I used to same object in my last project and it works, but it doesn't in my current project. When I remove the materials the lighting affects it, but when I add it, it is pitch black. The renderer renders continously. Also, there are no errors in the console.
Here is the code used in my current project: (MaterialLoader is an MTLLoader instance and ObjectLoader is an OBJLoader instance)
MaterialLoader.load("bbb/bbb.mtl",
function(materials) {
materials.preload()
ObjectLoader.setMaterials(materials)
ObjectLoader.load("bbb.obj",
function(model) {
let mesh = model.children[0]
scene.add(mesh)
}, null, function(error) {alert(error)}
)
}, null, function(error) {alert(error)}
)
Here is the code from my previous project (the loader variable is an OBJLoader instance, and the materials load successfully here.)
mtlLoader.load(
"bbb.mtl",
function(materials) {
materials.preload()
loader.setMaterials(materials)
loader.load("bbb.obj",
function(obj) {
level = obj.children[0]
scene.add(level)
}, null,
function(error) { alert(error) }
)
}, null,
function(error) { alert(error) }
)
https://discourse.threejs.org/t/objloader-mtlloader-not-loading-texture-image/2534
try change object material color, like this:
model.children[0].material.color.r = 1;
model.children[0].material.color.g = 1;
model.children[0].material.color.b = 1;
its work for me
Your code works when tested! Maybe it's an issue with the material export, uv unwrapping, the texture's path, do you have lighting added to the scene, etc. Here's my test code:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75,320/240,1,500);
camera.position.set( 0,2,2 );
camera.lookAt( scene.position );
var lightIntensity = 1;
var lightDistance = 10;
var light = new THREE.AmbientLight( 0xFFFFFF, lightIntensity, lightDistance );
light.position.set( 0, 5, 0 );
scene.add( light );
var grid = new THREE.GridHelper(10,10);
scene.add( grid );
var renderer = new THREE.WebGLRenderer({});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( 320,240 );
renderer.domElement.style.margin = '0 auto';
renderer.domElement.style.display = 'block';
renderer.domElement.style.backgroundColor = '#dddddd';
$(document.body).append(renderer.domElement);
function update(){
renderer.render( scene, camera );
requestAnimationFrame( update );
}
update();
mtl_loader = new THREE.MTLLoader();
mtl_loader.load("assets/doughnut.mtl",
function(materials) {
materials.preload()
var obj_loader = new THREE.OBJLoader();
obj_loader.setMaterials(materials)
obj_loader.load("assets/doughnut.obj",
function(object) {
let mesh = object.children[0]
scene.add(mesh);
}, null, function(error) {alert(error)}
)
}, null, function(error) {alert(error)}
);

Multiple scenes and multiple .obj models in Three.js

I am stuck with loading in two .obj models into two different div elements.
Currently, both objects end up in the last div element, as seen here:
https://casagroupproject.github.io/template1/test.html
My code is based on this example here:
https://threejs.org/examples/?q=mul#webgl_multiple_elements
I create an array with my models stored in:
var canvas;
var scenes = [], renderer;
var scene;
//external geometries
var models = {
tent: {
obj:"./assets/Tent_Poles_01.obj",
},
campfire: {
obj:"./assets/Campfire_01.obj",
}}
init();
animate();
I then load them in a loop in init():
for( var _key in models ){
scene = new THREE.Scene();
var element = document.createElement( "div" );
element.className = "list-item";
element.innerHTML = template.replace( '$', _key);
console.log('does this work', element.innerHTML);
scene.userData.element = element.querySelector( ".scene" );
content.appendChild( element );
var camera = new THREE.PerspectiveCamera( 50, 1, 1, 10 );
camera.position.z = 2;
scene.userData.camera = camera;
var objLoader = new THREE.OBJLoader();
objLoader.load(
models[_key].obj,
function ( object ) {
scene.add( object );
console.log(object);
},
);
}
And render them here:
renderer = new THREE.WebGLRenderer( { canvas: canvas, antialias: true } );
renderer.setClearColor( 0xffffff, 1 );
renderer.setPixelRatio( window.devicePixelRatio );
Why do both models end up in the last div?

Select, Update and Manipulate an Obj file using threejs

I am building a 3D Visualization and Interactive application using threejs.Following are the key functionalities I want to provide in this application:
In this User should be able to:
Rotate and Scale the Obj. -- done
Manipulate some certain parts of the Obj like, changing its color, replace that part with another. -- pending
I am following the vast threejs
documentation
and its list of examples, which
really helped me a lot and I am able to achieve a little.
Also I have come across an useful threejs inspector Chrome
Ext.
This threejs inspector Chrome Ext all in all does everything what I want to achieve, but unfortunately I am not able to understand that how does it work and how does it able to select and manipulate the parts of an Obj file.
I am using the following piece of code using threejs for now to just display, rotate and scale the Obj file.
Updated Code:
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container, camera, controls, scene, renderer, mesh;
var mtlObject = {};
var strDownloadMime = "image/octet-stream";
init();
animate();
function init() {
var saveLink = document.createElement('div');
saveLink.style.position = 'absolute';
saveLink.style.top = '10px';
saveLink.style.width = '100%';
saveLink.style.color = 'white !important';
saveLink.style.textAlign = 'center';
saveLink.innerHTML ='Save Frame';
document.body.appendChild(saveLink);
document.getElementById("saveLink").addEventListener('click', saveAsImage);
renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true
});
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 500;
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 2.0;
controls.zoomSpeed = 2.0;
controls.panSpeed = 2.0;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [ 65, 83, 68 ];
controls.addEventListener( 'change', render );
// world
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight( 0x444444 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 ).normalize();
scene.add( directionalLight );
// model
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round(percentComplete, 2) + '% downloaded' );
}
};
var onError = function ( xhr ) { };
//mtl loader
THREE.Loader.Handlers.add( /\.dds$/i, new THREE.DDSLoader() );
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath( 'obj/' );
mtlLoader.load( 'arm.mtl', function( materials ) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials( materials );
objLoader.setPath( 'obj/' );
objLoader.load( 'arm.obj', function ( object ) {
object.name = "object_name";
object.position.y = - 95;
scene.add( object );
//As 'TheJim01' suggested
//I have used an object variable.
//then traverse through the scene nodes
//and target some particular parts of the obj as:
var nameToObject = {};
scene.traverse( function( node ) {
nameToObject[node.name] = node;
if (node.name == ("Pad01")) {
node.visible = false;
}
if (node.name == ("Arm_01")) {
node.visible = false;
}
if (node.name == ("Pad02")) {
node.visible = false;
}
if (node.name == ("Arm_02")) {
node.visible = false;
}
});
}, onProgress, onError );
});
// lights
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
var light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
var light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
//
render();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
render();
}
function animate() {
requestAnimationFrame( animate );
controls.update();
}
function render() {
renderer.render( scene, camera );
}
//my next challenge is to save the canvas as image
//after making all the changes to the obj file
function saveAsImage() {
var imgData, imgNode;
try {
var strMime = "image/jpeg";
imgData = renderer.domElement.toDataURL(strMime);
saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg");
} catch (e) {
console.log(e);
return;
}
}
var saveFile = function (strData, filename) {
var link = document.createElement('a');
if (typeof link.download === 'string') {
document.body.appendChild(link); //Firefox requires the link to be in the body
link.download = filename;
link.href = strData;
link.click();
document.body.removeChild(link); //remove the link when done
} else {
location.replace(uri);
}
}
$(document).ready(function() {
//Set Color of the Obj parts accordingly
$('#armblock').on('click', function(){
$(this).children('ul').slideToggle(400);
$(this).children('ul').children('li').on('click', function(){
$color = new THREE.Color($(this).css('backgroundColor'));
var selectedColor = '#' + $color.getHexString();
//As 'TheJim01' suggested
//I have used and object variable.
//then traverse through the scene nodes
//and target some perticular parts of the obj as:
var nameToObject = {};
scene.traverse( function( node ) {
nameToObject[node.name] = node;
if (node.name == ("Arm_01")) {
node.visible = true;
nameToObject["Arm_01"].material.color.set(selectedColor);
}
if (node.name == ("Arm_02")) {
node.visible = true;
nameToObject["Arm_02"].material.color.set(selectedColor);
}
});
});
});
$('#padblock').on('click', function(){
$(this).children('ul').slideToggle(400);
$(this).children('ul').children('li').on('click', function(){
$color = new THREE.Color($(this).css('backgroundColor'));
var selectedColor = '#' + $color.getHexString();
//As 'TheJim01' suggested
//I have used an object variable.
//then traverse through the scene nodes
//and target some particular parts of the obj as:
var nameToObject = {};
scene.traverse( function( node ) {
nameToObject[node.name] = node;
if (node.name == ("Pad01")) {
node.visible = true;
nameToObject["Pad01"].material.color.set(selectedColor);
}
if (node.name == ("Pad02")) {
node.visible = true;
nameToObject["Pad02"].material.color.set(selectedColor);
}
});
});
});
});
Please if anyone can help me out in this.
Thanks in advance and please comment if I am missing anything.
Update
Next Challenges
I am able to change the color of a particular node(part of the obj) but its not spontaneous as I have to click on the canvas/obj again to see the changes.
I am able to hide/show a particular node(part of the obj) but I want to replace that particular node(part of the obj) with another one.
I want to save the final obj file after making all the changes as an Image, but in future as an gif or video so that user can visualize 360deg view of the final product.
PS
TheJim01 helped me a lot into understanding the basic but very important concept of traversing the obj file and its parts.
That leads me to build at least closer to something what I want.
All three.js inspector is doing is parsing the scene, and displaying the various properties of the objects in an interactive UI.
Let's say you have an OBJ file arranged like this:
bike
frame
seat
drive
pedals
frontSprocket
chain
rearSprocket
rearWheel
steering
handlebars
fork
frontWheel
OBJLoader would create a scene hierarchy like this:
bike // THREE.Group
frame // THREE.Mesh
seat // THREE.Mesh
drive // THREE.Group
pedals // THREE.Mesh
frontSprocket // THREE.Mesh
chain // THREE.Mesh
rearSprocket // THREE.Mesh
rearWheel // THREE.Mesh
steering // THREE.Group
handlebars // THREE.Mesh
fork // THREE.Mesh
frontWheel // THREE.Mesh
three.js inspector displays this same hierarchy, using the names of the objects. When you click on an object in its tree, it references the object in the scene, and grabs/displays its properties, such as its position, rotation, visible state, etc. When you make a change in the three.js inspector UI, it sets the value on the object in the scene, resulting in the changes you see.
You can do all of this yourself, and you don't even need to be so general about it. Say you want to create a map of object name to the scene object for easier reference (searching the scene is fast enough, but it's recursive). So you could do this:
var nameToObject = {};
scene.traverse(function(node){
// watch out for duplicate empty names!
nameToObject[node.name] = node;
});
(That doesn't give you the hierarchy, but this is just an example.)
Now you can get and update any object by name:
// enlarge the rear tire
nameToObject["rearTire"].scale.addScalar(0.1);
You can read and set all properties of the object. For example, if MTLLoader created a basic material for the frame, you could do something like this:
// make the frame red
nameToObject["frame"].material.color.setRGB(1.0, 0.0, 0.0);
Or you could outright replace the entire material.
For your example of replacing an object, let's say you already loaded a new Mesh called newRearTire...
// replace the rear tire
var drive = nameToObject["drive"]; // the parent of rearTire
drive.remove(nameToObject["rearTire"]);
drive.add(newRearTire);
(Of course you would need to re-build your name map at this point.)
These are just very general examples, but should get you started. If you encounter any problems accessing your data, leave a comment and I'll try to clarify.

ColladaLoader can't load two different models

If I run the example below it gives me this error:
Uncaught TypeError: Cannot read property 'x' of undefinedt # three.min.js:462renderBuffer # three.min.js:549k # three.min.js:450render # three.min.js:561render # loaderTest.html:46
This is the last line where it calls render()
If I load two times the same model with loader.load(..) the error does not happen, it only occurs when I choose different models.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var light = new THREE.AmbientLight( 0xFFFFFF );
scene.add( light );
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load("models/model1.dae", function(colladaModel){
var model = colladaModel.scene;
model.position.set(0,0,0);
scene.add(model);
});
loader.load("models/model2.dae", function(colladaModel){
var model = colladaModel.scene;
model.position.set(20,0,0);
scene.add(model);
});
camera.position.z = 100;
var render = function () {
requestAnimationFrame( render );
renderer.render(scene, camera);
};
render();
You're trampling on the model variable. The callback that you've provided doesn't run until the model is loaded, but it has already started loading the other one as well.
model = colladaModel.scene;
You didn't declare your variable model, which makes it a global variable which is thus shared browser-wide and between those two callbacks). It's actually window.model that you're using there.
Change them to:
var model = colladaModel.scene;

Categories