Apply MeshBasicMaterial to loaded obj+mtl with Three.js - three.js

When I load obj+mtl+jpg data with Three.js, the material of the object is set to MeshLambertMaterial according to three.js document. If I want to change the material to another one such as MeshBasicMaterial, how can I do that?
The following code loads obj+mtl+jpg from my server and display the data with MeshLambertMaterial. I tried to apply MeshBasicMaterial with the following code (commented), but it failed and the object with displayed in white.
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"></head>
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/loaders/MTLLoader.js"></script>
<script src="http://threejs.org/examples/js/loaders/OBJMTLLoader.js"></script>
<body>
<div id="canvas_frame"></div>
<script>
var canvasFrame, scene, renderer, camera;
canvasFrame = document.getElementById('canvas_frame');
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
canvasFrame.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(50,50,50);
camera.lookAt( {x: 0, y: 0, z: 0} );
var ambient = new THREE.AmbientLight(0xFFFFFF);
scene.add(ambient);
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
function loadObjMtl(objUrl, mtlUrl, url, x, y, z){
var loader = new THREE.OBJMTLLoader();
loader.crossOrigin = 'anonymous';
loader.load( objUrl, mtlUrl,
function ( object ) {
object.url = url;
object.position.set(x,y,z);
object.traverse( function( node ) {
if( node.material ) {
////This doesn't work. How can I apply material for loaded obj?
//var material = new THREE.MeshBasicMaterial();
//if ( node instanceof THREE.Mesh ) {
// node.material = material;
//}
}
});
scene.add ( object );
});
}
objUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.obj";
mtlUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.mtl";
loadObjMtl(objUrl,mtlUrl,"",0,0,0);
animate();
</script>
</body>
</html>

You see it totally white because you are not specifying any color for MeshBasicMaterial.
Try this:
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
You'll see all red: it is working.
I think that you probably want to distinguish the different meshes, or apply any texture, do you?

I know this is an old question but I was looking to do something similar and did it this way:
As of today Three.js r143 , the MTLLoader loads the materials as MeshPhongMaterial as seen in
MTLLoader.js 495: this.materials[ materialName ] = new MeshPhongMaterial( params );
Because I didn't want any reflections from my lighting. So I set the materials to
MeshLambertMaterial (see the docs) by just changing that line to:
MTLLoader.js 495: this.materials[ materialName ] = new MeshLambertMaterial( params );
And that's it!.
I know this approach will change the way every other model is loaded but in my case that's how I wanted it.
Hope this helps

Related

one OrbitControls with two scenes

I want to sync the mouse control of two .dae files (in two scenes side-by-side) with OrbitControls. I can control any one object individually with any one of my scenes but any attempt to control both objects in sync fails.
It seems like I can only have one OrbitControls instance. Any one of the following 'controls#' lines works on its own but as soon as I have both, only the first one is operative:
controls1 = new OrbitControls( camera1, renderer1.domElement );
controls2 = new OrbitControls( camera2, renderer2.domElement );
I am a chemistry prof, not a programmer.Any help is gratefully appreciated. Thanks!
<html lang="en">
<head>
<title>GD - 2 mols</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<h3> VB orbital visualizer: 2 molecules</h3>
<div id="molcontainer1"></div>
<div id="molcontainer2"></div>
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims#1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../three/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { ColladaLoader } from '../three/examples/jsm/loaders/ColladaLoader.js';
import { OrbitControls } from '../three/examples/jsm/controls/OrbitControls.js';
let molcontainer1, molcontainer2, camera1, camera2, scene1, scene2, renderer1, renderer2, mol1, mol2;
init();
//animate();
function init() {
molcontainer1 = document.getElementById( 'molcontainer1' );
molcontainer2 = document.getElementById( 'molcontainer2' );
//scene, camera, lighting, etc
scene1 = new THREE.Scene();
scene1.background = new THREE.Color( 0xbbffff );
scene2 = new THREE.Scene();
scene2.background = new THREE.Color( 0xffcccc );
camera1 = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
camera1.position.set( 0.4, 0.4, 0.4 );
camera1.lookAt( 0, 0, 0 );
camera2 = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
camera2.position.set( 0.4, 0.4, 0.4 );
camera2.lookAt( 0, 0, 0 );
const ambientLight1 = new THREE.AmbientLight( 0xffffff, 1 ); // was 0xcccccc
scene1.add( ambientLight1 );
const ambientLight2 = new THREE.AmbientLight( 0xffffff, 1 ); // was 0xcccccc
scene2.add( ambientLight2 );
const directionalLight1 = new THREE.DirectionalLight( 0xffffff, 0.8 );
directionalLight1.position.set( 1, 1, 1 ).normalize();
scene1.add( directionalLight1 );
const directionalLight2 = new THREE.DirectionalLight( 0xffffff, 0.8 );
directionalLight2.position.set( 1, 1, 1 ).normalize();
scene2.add( directionalLight2 );
renderer1 = new THREE.WebGLRenderer();
renderer1.outputEncoding = THREE.sRGBEncoding;
renderer1.setPixelRatio( window.devicePixelRatio );
renderer1.setSize( window.innerWidth/2, window.innerHeight/2 );
molcontainer1.appendChild( renderer1.domElement );
renderer2 = new THREE.WebGLRenderer();
renderer2.outputEncoding = THREE.sRGBEncoding;
renderer2.setPixelRatio( window.devicePixelRatio );
renderer2.setSize( window.innerWidth/2, window.innerHeight/2 );
molcontainer2.appendChild( renderer2.domElement );
// loading manager
const loadingManager = new THREE.LoadingManager( function () {
scene1.add( mol1 );
scene2.add( mol2 );
} );
// assign collada .dae file
const loader1 = new ColladaLoader( loadingManager );
loader1.load( './models/molecule.dae', function ( collada ) {
mol1 = collada.scene;
animate();
render();
} );
const loader2 = new ColladaLoader( loadingManager );
loader2.load( './models/CH2CHO.dae', function ( collada ) {
mol2 = collada.scene;
animate();
render();
} );
// 3d mouse controls
controls1 = new OrbitControls( camera1, renderer1.domElement );
controls2 = new OrbitControls( camera2, renderer2.domElement );
controls1.addEventListener( 'change', render );
controls2.addEventListener( 'change', render );
controls1.target.set( 0, 0, 0 );
controls2.target.set( 0, 0, 0 );
//controls.update();
window.addEventListener( 'resize', onWindowResize );
}
function animate() {
requestAnimationFrame( animate);
window.addEventListener( 'resize', onWindowResize ); // just added this
render();
}
function onWindowResize() {
camera1.aspect = window.innerWidth / window.innerHeight;
camera1.updateProjectionMatrix();
renderer1.setSize( window.innerWidth/2, window.innerHeight/2 );
camera2.aspect = window.innerWidth / window.innerHeight;
camera2.updateProjectionMatrix();
renderer2.setSize( window.innerWidth/2, window.innerHeight/2 );
render();
}
function render() {
renderer1.render( scene1, camera1 );
renderer2.render( scene2, camera2 );
//controls.update();
}
</script>
</body>
</html>```
You actually only need one camera and one OrbitControls to drive the two renderers and Scenes. The reason for this is that neither is intrinsically tied to the renderer or the Scene; the camera basically just tracks a group of variables that, on each frame, allow the renderer to compute its projection, and the OrbitControls only cares about the user's interaction with (a defined portion of) the page and the camera whose values it needs to mutate. (Note, as well, that neither a camera nor an OrbitControls takes any reference to a renderer or Scene when you initialize it, and most examples you see don't use Scene.Add() to add the camera to the scene graph.)
So, your code needs the following modifications.
First, wrap your molcontainer* divs inside of another div, and give it an ID of, say, molwrapper. This wrapper div will define the area on the page (comprising the two scenes) that will receive the user interaction for the OrbitControls. Use document.getElementById() to store a reference to molwrapper.
Next, remove all references to camera2 and rename camera1 to camera, and do the same with controls2 and controls1.
Finally, initialize controls (which used to be controls1) by passing it references to camera (which used to be camera1) and molwrapper.
I've posted a working example on CodePen.

how to update shape (not geometry) in three.js?

I am trying to customize a shape by a slider, and then the shape is extruded by ExtrudeGeometry. First, I created 4 points as in "pts", and then created a "shape" with them, and finally, created a geomtery by extrude it through a line. However, I cannot make it works. My HTML code is as below:
<head>
<script src="three.min.js"> </script>
<script src="three.js"> </script>
<script src="TrackballControls.js"></script>
<script src="main.js"> </script>
</head>
<body onload="start()">
<div id="s"> </div>
</body>
</html>
and the "main" js file is as follows:
var camera, scene, renderer, phone, material, mesh;
var shape, extrudeSettings, randomSpline, controls;
var pts = [];
var randomPoints = [];
var v;
function start() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,1,10000);
camera.position.set( 0, 0, 500 );
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0x222222 );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.minDistance = 100;
controls.maxDistance = 600;
scene.add(camera);
pts.push( new THREE.Vector3 (100,1,0));
pts.push( new THREE.Vector3 (-100,1,0));
pts.push( new THREE.Vector3 (-100,-1,0));
pts.push( new THREE.Vector3 (100,-1,0));
randomPoints.push( new THREE.Vector3 (0,0,10));
randomPoints.push( new THREE.Vector3 (0,0,-10));
randomSpline = new THREE.SplineCurve3( randomPoints );
extrudeSettings = {
steps : 200,
bevelEnabled : false,
extrudePath : randomSpline
};
shape = new THREE.Shape(pts);
phone = new THREE.ExtrudeGeometry(shape, extrudeSettings);
material = new THREE.MeshBasicMaterial({color:0x0000FF, wireframe:true});
mesh = new THREE.Mesh(phone,material);
scene.add(mesh);
var slider = document.createElement("input");
slider.setAttribute("type", "range");
slider.setAttribute("value", 90);
slider.setAttribute("id", "sliding");
document.getElementById("s").appendChild(slider);
document.getElementById("s").style.position = "absolute";
document.getElementById("s").style.zIndex="1";
animate();
}
function animate() {
requestAnimationFrame(animate);
v = document.getElementById("sliding").value;
pts[0].x = v;
pts[1].x = -v;
pts[2].x = -v;
pts[3].x = v;
shape.needsUpdate = true;
phone.verticesNeedUpdate = true;
phone.dynamic=true;
controls.update();
renderer.render( scene, camera );
}
When you create an ExtrudeGeometry, it creates the geometry based on the parameter, but it doesn't store the object you created it from. You'll need to create a new ExtrudeGeometry each time you want to update it, or you may be able to simply adjust phone.scale, depending on your needs.

Basic three.js collada skinned animation

I'm trying to get a really basic Collada model to animate in three.js but I'm having some issues. The two examples (the monster and pump) both work on my machine, but whenever I substitute my model then it will load but it won't animate.
I stripped out a lot of the extra code from the examples and tried to make a really basic script. Here's my code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - collada</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body>
<script src="../build/three.min.js"></script>
<script src="js/loaders/ColladaLoader.js"></script>
<script src="js/Detector.js"></script>
<script>
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container;
var camera, scene, renderer, objects;
var dae, skin, animation, kfAnimation;
var clock = new THREE.Clock();
var loader = new THREE.ColladaLoader();
loader.load( './obj/Test1/TestSKINNED_Animation01.dae', function ( collada ) {
dae = collada.scene;
skin = collada.skins[ 0 ];
animation = collada.animations[0];
dae.scale.x = dae.scale.y = dae.scale.z = 1;
init();
animate();
} );
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
// Add the camera
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.set( 10, 2, 0 );
camera.lookAt( scene.position );
// Add the Collada
scene.add( dae );
var animHandler = THREE.AnimationHandler;
animHandler.add( animation );
kfAnimation = new THREE.KeyFrameAnimation( animation.node, animation.name );
kfAnimation.timeScale = 1;
kfAnimation.play( true, 0 );
// Add the light
var directionalLight = new THREE.DirectionalLight( 0xeeeeee );
directionalLight.position.set(0.5, 0.5, 0.5);
scene.add( directionalLight );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
}
function animate() {
var delta = clock.getDelta();
kfAnimation.update(delta);
render();
requestAnimationFrame( animate );
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
</html>
It loads the model but it doesn't animate. Is this likely a problem with the code or the model? Thanks.
From what I understood from kfAnimations is that they work for JSON files, you're working with a DAE file so youll just have to use the skin.influences way of animating a model:
function animate() {
var delta = clock.getDelta();
if ( t > 1 ) t = 0;
if ( skin ) {
for ( var i = 0; i < skin.morphTargetInfluences.length; i++ ) {
skin.morphTargetInfluences[ i ] = 0;
}
skin.morphTargetInfluences[ Math.floor( t * 30 ) ] = 1;
t += delta;
}
requestAnimationFrame(animate);
render();
}
I am also facing the same problem but by comparing closely my collada file and the pump.dae sample, I can deduce that the THREE.ColladaLoader can not load animation that uses the LINEAR interpolation. The pump.dae sample actually uses the BEZIER interpolation.
When using my collada files the collada.animations property actually returns an empty vector as if no animation were found whereas using it with the pump.dae sample you get the exact number of animation node in the file.
you need yo add traverse function inside your load to render animations
loader.load( './obj/Test1/TestSKINNED_Animation01.dae', function ( collada ) {
dae = collada.scene;
skin = collada.skins[ 0 ];
animation = collada.animations[0];
dae.scale.x = dae.scale.y = dae.scale.z = 1;
dae.traverse( function ( child ) {
if ( child instanceof THREE.SkinnedMesh ) {
var animation = new THREE.Animation( child, child.geometry.animation );
animation.play();
}
});
init();
animate();
} );
user674756 is right, there are important limitations in the Collada loader for three.js, and it looks like they won't get fixed soon. A full list of the limitations is at
https://github.com/mrdoob/three.js/issues/2963
There is, though, a tool which tries to load a .dae file, simplify it and convert it into a JSON which should prove more malleable. You can use it inline here.
http://rmx.github.io/collada-converter/preview/examples/convert.html
There's also a preview, so you can check if your animation comes through.

Add subdivision to a geometry

I'm trying add subdivisions to a sphere like this:
http://stemkoski.github.com/Three.js/Subdivision-Cube.html
Here's my code: http://jsfiddle.net/mdrorum/HvFLw/
<script src="http://mrdoob.github.com/three.js/build/three.js"></script>
<script type="text/javascript">
var camera, scene, renderer;
var geometry, material, mesh;
var smooth, subdiv, modifier;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
geometry = new THREE.SphereGeometry( 200 );
material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
var smooth = mesh.clone();
var subdiv = 3;
var modifier = new THREE.SubdivisionModifier( subdiv );
//modifier.modify( smooth );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render( scene, camera );
}
</script>
This works fine, but uncomment: //modifier.modify( smooth );
Nothing happens. :(
How I can add subdivisions?
Here you can find a good tutorial with a working demo. Citing the author:
// First we want to clone our original geometry.
// Just in case we want to get the low poly version back.
var smooth = THREE.GeometryUtils.clone( geometry );
// Next, we need to merge vertices to clean up any unwanted vertex.
smooth.mergeVertices();
// Create a new instance of the modifier and pass the number of divisions.
var divisions = 3;
var modifier = new THREE.SubdivisionModifier(divisions);
// Apply the modifier to our cloned geometry.
modifier.modify( smooth );
// Finally, add our new detailed geometry to a mesh object and add it to our scene.
var mesh = new THREE.Mesh( smooth, new THREE.MeshPhongMaterial( { color: 0x222222 } ) );
scene.add( mesh );
You are trying to modify a mesh. You need to modify a geometry.
modifier.modify( geometry );

Three.js Ellipse

How does one go about creating an ellipse in three.js?
I've looked at this:
Drawing an ellipse in THREE.js
But it would be cool if someone could provide a working example.
I've tried this:
ellipse = new THREE.EllipseCurve(0,0,200,400,45,45,10);
but that's not working for me. I have no idea what the parameters mean so I'm just blindly going about it.
edit: I am getting the error "defined is not a function" when I try to create an ellipse curve.
edit2: Figured out I had to include Curves.js for it to work but having a working example somewhere would still be really nice for me and other people since the stackoverflow link I pasted earlier doesn't have an example.
I am unfamiliar with THREE.js, but looking at the code the parameters seem to be
(Center_Xpos, Center_Ypos, Xradius, Yradius, StartAngle, EndAngle, isClockwise)
so a reason your definition isn't working is because you're setting the start and end angles both to the same thing.
There is an example here, and below is my example:
var scene = new THREE.Scene();
var material = new THREE.LineBasicMaterial({color:0x000000, opacity:1});
var ellipse = new THREE.EllipseCurve(0, 0, 1, 5, 0, 2.0 * Math.PI, false);
var ellipsePath = new THREE.CurvePath();
ellipsePath.add(ellipse);
var ellipseGeometry = ellipsePath.createPointsGeometry(100);
ellipseGeometry.computeTangents();
var line = new THREE.Line(ellipseGeometry, material);
scene.add( line );
Notice: this is not a complete example, you should add the rest code like <script src="js/three.min.js"></script> if you want to view the result.
You can directly set a circle geometry and set its scale as: object.scale.setY(2.5);
This could easily form an oval without using any shape or curve.
Here is a complete working example.
<!doctype html>
<html>
<head>
<title>example</title>
<script src="threejs/build/three.min.js"></script>
<script src="threejs/src/core/Curve.js"></script>
<script src="threejs/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<script>
var parent, renderer, scene, camera, controls, pivot1, pivot2, pivot3;
init();
animate();
function init() {
// info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '30px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.style.color = '#fff';
info.style.fontWeight = 'bold';
info.style.backgroundColor = 'transparent';
info.style.zIndex = '1';
info.style.fontFamily = 'Monospace';
info.innerHTML = 'Drag your cursor to rotate camera';
document.body.appendChild( info );
// renderer
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.physicallyBasedShading = true;
document.body.appendChild( renderer.domElement );
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 20, 20, 20 );
// controls
controls = new THREE.OrbitControls( camera );
// axes
scene.add( new THREE.AxisHelper( 20 ) );
var material = new THREE.LineBasicMaterial({color:0x000000, opacity:1});
var ellipse = new THREE.EllipseCurve(0, 0, 1, 4, 0, 2.0 * Math.PI, false);
var ellipsePath = new THREE.CurvePath();
ellipsePath.add(ellipse);
var ellipseGeometry = ellipsePath.createPointsGeometry(100);
ellipseGeometry.computeTangents();
var line = new THREE.Line(ellipseGeometry, material);
scene.add( line );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
</script>
</body>
</html>

Resources