Multiple scenes and multiple .obj models in Three.js - javascript

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?

Related

ThreeJS Upload multiple textures for .obj

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.

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)}
);

.obj file in javascript is not moving when using keyboard

The most important part in my code is in JavaScript. I want that my objects can move when I press "down" or "right" or etc. The problem is that object from .json file is moving, but object from .obj file is not. Is it possible to fix that?
This is my code:
<script>
var container, scene, camera, renderer, controls, stats;
var keyboard = new THREEx.KeyboardState();
var keyboard = new KeyboardState();
var galva;
init();
animate();
render();
function init()
{
var spgeometry = new THREE.SphereGeometry( 30, 32, 32 );
var spmaterial = new THREE.MeshBasicMaterial( {color: 0x00000} );
galva = new THREE.Mesh( spgeometry, spmaterial );
galva.position.set(125, 150, 90);
scene.add( galva );
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load( "models/android.js", addModelToScene );
var loader = new THREE.OBJLoader();
loader.load("models/teaport.obj", function ( object ) {
var material = new THREE.MeshLambertMaterial( );
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = material;
}
} );
object.scale.set(50,50,50);
scene.add( object );
} );
}
function addModelToScene( geometry, materials )
{
var material = new THREE.MeshLambertMaterial( materials );
c = new THREE.Mesh( geometry, material );
c.scale.set(50,50,50);
scene.add( c );
}
function update()
{
keyboard.update();
if ( keyboard.down("left") ){
galva.translateX( -50 );
}
if ( keyboard.down("right") ){
c.translateX( 50 );
galva.translateX( 50 );
object.translateX( 50 );
}
controls.update();
stats.update();
}
If you put together a jsfiddle or something similar - it could be easier to figure out. I think you end up with variable shadowing, and your "object" is simply undefined. Try following change.
var loader = new THREE.OBJLoader();
loader.load("models/teaport.obj", function ( o ) {
var material = new THREE.MeshLambertMaterial( );
o.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = material;
}
} );
o.scale.set(50,50,50);
scene.add( o );
object = o; // <-- important

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.

backbone.js - Having multiple instances of the same view

I am having problems having multiple instances in of the same view in different div elements. When I try to initialize them only the second of the two elements appear no matter what order I put them in.
Here is the code for my view.
var BodyShapeView = Backbone.View.extend({
thingiview: null,
scene: null,
renderer: null,
model: null,
mouseX: 0,
mouseY: 0,
events:{
'click button#front' : 'front',
'click button#diag' : 'diag',
'click button#in' : 'zoomIn',
'click button#out' : 'zoomOut',
'click button#on' : 'rotateOn',
'click button#off' : 'rotateOff',
'click button#wireframeOn' : 'wireOn',
'click button#wireframeOff' : 'wireOff',
'click button#distance' : 'dijkstra'
},
initialize: function(name){
_.bindAll(this, 'render', 'animate');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 15, 400 / 700, 1, 4000 );
camera.position.z = 3;
scene.add( camera );
camera.position.y = -5;
var ambient = new THREE.AmbientLight( 0x202020 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.75 );
directionalLight.position.set( 0, 0, 1 );
scene.add( directionalLight );
var pointLight = new THREE.PointLight( 0xffffff, 5, 29 );
pointLight.position.set( 0, -25, 10 );
scene.add( pointLight );
var loader = new THREE.OBJLoader();
loader.load( "img/originalMeanModel.obj", function ( object ) {
object.children[0].geometry.computeFaceNormals();
var geometry = object.children[0].geometry;
console.log(geometry);
THREE.GeometryUtils.center(geometry);
geometry.dynamic = true;
var material = new THREE.MeshLambertMaterial({color: 0xffffff, shading: THREE.FlatShading, vertexColors: THREE.VertexColors });
mesh = new THREE.Mesh(geometry, material);
model = mesh;
// model = object;
scene.add( model );
} );
// RENDERER
renderer = new THREE.WebGLRenderer();
renderer.setSize( 400, 700 );
$(this.el).find('.obj').append( renderer.domElement );
this.animate();
},
Here is how I create the instances
var morphableBody = new BodyShapeView({ el: $("#morphable-body") });
var bodyShapeView = new BodyShapeView({ el: $("#mean-body") });
Any help would be really appreciated.
Thanks in advance.
In your initializemethod, you access global variables instead of instance variables: scene = new THREE.Scene(); will in fact reference window.scene.
Check this example
var BodyShapeView = Backbone.View.extend({
scene:null,
initialize:function(opts) {
scene=opts.scene;
}
});
var s1=new BodyShapeView({scene:'s1'});
var s2=new BodyShapeView({scene:'s2'});
console.log(window.scene); //outputs s2
console.log(s1.scene); //outputs null
http://jsfiddle.net/K8tjJ/1/
Change these references to this.scene, this.camera, etc.
var BodyShapeView = Backbone.View.extend({
scene:null,
initialize:function(opts) {
this.scene=opts.scene;
}
});
var s1=new BodyShapeView({scene:'s1'});
var s2=new BodyShapeView({scene:'s2'});
console.log(window.scene); //outputs undefined
console.log(s1.scene); //outputs s1
http://jsfiddle.net/K8tjJ/2/
Additionally, loader.load uses a callback to process its results, you will (probably) lose the reference to this in the function. Try
var _this=this;
loader.load( "img/originalMeanModel.obj", function ( object ) {
...
_this.scene.add( _this.mesh ); // or _this.model if you prefer, but beware
});
Note that this.model is a special variable in Backbone.View and should be treated with care if you ever want to pass a model to your view.
The el attribute only needs a jQuery style selector string. Try:
var morphableBody = new BodyShapeView({ el: "#morphable-body" });
var bodyShapeView = new BodyShapeView({ el: "#mean-body" });
UPDATE
Also, your problem may be that you're not using the this keyword when defining scene, camera, and renderer. It could be that the second view is overwriting the first view as a result. Try:
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera( 15, 400 / 700, 1, 4000 );
...
// RENDERER
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize( 400, 700 );
$(this.el).find('.obj').append( this.renderer.domElement );

Categories