THREE.js loading .OBJ files - why do some render flat? - three.js

I am using the same general purpose code to load basic mesh objects (no textures) from OBJ files (obtained free from sites on the web).
Some objects render OK in that different faces reflect light with different intensities towards the camera. But other objects just render a uniform matt color and so their form cannot be seen.
I have looked inside the files and noticed various differences. For example one successful file contains records with the following initial characters v, vt, vn, f, s (vertex, vertext texture, vertex normal, face, smooth). An example of an unsuccessful file has these initial characters: v, vt, f.
Here is the code which I am using.
function F_Load_OBJ_Model ( givenFilespec, givenName, givenScene, givenHexColorStr, posX, posY, posZ, rotX, rotY, rotZ, scaleX, scaleY, scaleZ )
{
var OBJLoader = new THREE.OBJLoader();
OBJLoader.load( givenFilespec, F_make_LoadedOBJ_Handler ( givenName, givenScene, givenHexColorStr, posX, posY, posZ, rotX, rotY, rotZ, scaleX, scaleY, scaleZ ) );
}
function F_make_LoadedOBJ_Handler( givenName, givenScene, givenHexColorStr, posX, posY, posZ, rotX, rotY, rotZ, scaleX, scaleY, scaleZ )
{
//... See Tapio's answer (callback factory for use in loops)
//... at http://stackoverflow.com/questions/10920372/associate-loaded-json-files-with-a-name-for-later-access
//... This is useful in case have a loop loading multiple objects asynchronously.
return function ( object )
{
//... Note that <object> is a THREE Object3D which can have 1 or more child meshes.
//... But I will assume that there is only one child mesh.
//var material = new THREE.MeshBasicMaterial();
//eval( givenName + " = new THREE.Mesh( geometry, material );" );
//eval ( " var thisMeshOb =" + givenName );
//eval( givenName + " = object.children[0];" ); //....OK alias
eval( givenName + " = object.children[0].clone();" );//... also OK
//object.delete(); //... not possible or neccy, javascript will delete any object which cannot be referred to again.
eval ( " var thisMeshOb =" + givenName );//... alias used for following common local commands
thisMeshOb.position.set( posX, posY, posZ );
thisMeshOb.rotation.set( rotX, rotY, rotZ );
thisMeshOb.name = givenName;
thisMeshOb.scale.set( scaleX, scaleY, scaleZ );
givenScene.add( thisMeshOb );
//xxx thisMeshOb.material.type = THREE.MeshLambertMaterial();
thisMeshOb.material.color.setHex( givenHexColorStr ) ;//...("0xff0000");
thisMeshOb.geometry.computeFaceNormals(); //...no effect
thisMeshOb.geometry.normalsNeedUpdate = true; //... no effect
//... for PHONG ... floorGeometry.computeVertexNormals();
thisMeshOb.updateMatrix(); //... without this the next is not effective!?
xxx = SOW_F_grob_Add_to_Target_Set( thisMeshOb );
};
}//... EOF F_make_LoadedOBJ_Handler.
I guessed that it might be something to do with missing vertex normals. But adding the code to update them had no effect on the end result.
So is there a problem with the files or can I do something in THREE.js to make them render properly?

You've found correctly that the flat-looking files have no "vn" info.
"vn" means "vertex normal" -- that is, the direction that the (smoothed) normal is facing.
You can try calling something like thisMeshOb.geometry.computeFaceNormals(); and possibly follow it with thisMeshOb.geometry.computeVertexNormals(); -- then set thisMeshOb.geometry.normalsNeedUpdate = true; -- this will calculate a new set of normals directly inside THREE.
You only need to call this once per session, at read-in time, btw -- no need to recalculate every frame.

Related

Set target of directional light in THREE.js

I have model far away from the origin, and I want a directional light to hit the model like sunlight would do.
I set a position and a target for my DirectionalLight:
export const dirLight = getDirectional();
function getDirectional() {
const dirLight = new DirectionalLight( 0xffffff, 1 );
dirLight.position.set( 585000 + 10000, 6135000 + 10000, -500 + 5000);
return dirLight;
};
const helper = new THREE.DirectionalLightHelper( dirLight, 1000 );
let t = new THREE.Object3D();
t.translateX(585000);
t.translateY(6135000);
t.translateZ(1000);
dirLight.target = t;
scene.add(dirLight);
scene.add(dirLight.target);
scene.add(t);
helper.update();
scene.add( helper );
I would expect the light direction now to be parallel to vector between light position and light target, but apparently the light direction is still towards the origin of the scene. What am I doing wrong ?
A running example can be seen here
The documentation states that the target needs to be added to the scene so that the world coordinates are calculated. However, that does not seem to work.
So, instead I tried manually updating the world coordinates, and that worked. Probably that will only work with a static target.
In your case that would be adding
dirLight.target.updateMatrixWorld();

Three.js specific render-time uniforms

I want to implement per-object motion-blur effect based on calculating previous pixel position inside shaders.
This technic's first step is to build velocity map of moving objects. This step requirements is to have as uniform variables projection and model view matrices of current frame and the same matrices of previous frame.
How could I include those matrices to uniforms for some special shader? I supposed to have solution in some way like:
uniforms = {
some_uniform_var : {type: "m4", value: initialMatrix, getter: function(){
// `this` points to object
return this.worldMatrix
}}
}
But now in THREE.js this is not available. We could make some sort of monkey patching, but I cannot find best way to do it.
Any suggestions?
The current solvation to this problems consist of several parts. I'm using EffectComposer to make several passes of rendered scene, one of then - VelocityPass. It takes current and previous model-view matrix and projection matrix and produces two positions. Both of them then used to calculate speed of a point.
Shader looks like this
"void main() {",
"vec2 a = (pos.xy / pos.w) * 0.5 + 0.5;",
"vec2 b = (prevPos.xy / prevPos.w) * 0.5 + 0.5;",
"vec2 oVelocity = a - b;",
"gl_FragColor = vec4(oVelocity, 0.0, 1.);",
"}"
There're several issues of this decision.
Three.js has certain point where it injects matrices to object-related shaders. The very ending of SetProgram closure, which lives in WebGLRenderer. That's why I took the whole renderer file, renamed renderer to THREE.MySuperDuperWebGLRenderer and added couple lines of code in it:
A closure to access closures, defined in userspace:
function materialPerObjectSetup(material, object){
if( material.customUniformUpdate ){
material.customUniformUpdate( object, material, _gl ); // Yes, I had to pass it...
}
}
And calling of it in renderBuffer and renderBufferDirect;
var program = setProgram( camera, lights, fog, material, object );
materialPerObjectSetup(material, object);
Now - the userspace part:
velocityMat = new THREE.ShaderMaterial( THREE.VelocityShader );
velocityMat.customUniformUpdate = function(obj, mat, _gl){
// console.log("gotcha");
var new_m = obj.matrixWorld;
var p_uniforms = mat.program.uniforms;
var mvMatrix = camera.matrixWorldInverse.clone().multiplyMatrices(camera.matrixWorldInverse, obj._oldMatrix );
_gl.uniformMatrix4fv( p_uniforms.prevModelViewMatrix, false, mvMatrix.elements );
_gl.uniformMatrix4fv( p_uniforms.prevProjectionMatrix, false, camera.projectionMatrix.elements );
obj._pass_complete = true; // Необходимо сохранять состояние старой матрицы пока не отрисуется этот пасс.
// А то матрицы обновляются каждый рендеринг сцены.
}
_pass_complete needed when we rerendering scene several times - each time matrix recalculated. This trick help us save previous matrix untill we use it.
_gl.uniformMatrix4fv is needed, because three.js serves universes one time before rendering. No matter how much objects we have - other method will pass to the shader modelViewMatrix of the last one. This happens because I want to draw this scene fully using VelocityShader. There's no other way to say to Renderer to use some alternative material for objects.
And as final point of this explaination I putting here a trick to manage previous matrix of an object:
THREE.Mesh.prototype._updateMatrixWorld = rotatedObject.updateMatrixWorld;
THREE.Mesh.prototype._pass_complete = true;
Object.defineProperty(THREE.Mesh.prototype, "updateMatrixWorld", {get: function(){
if(this._pass_complete){
this._oldMatrix = this.matrixWorld.clone();
this._pass_complete = false;
}
this._updateMatrixWorld();
return (function(){
});
}})
I believe, that there's could be a nicer solution. But sometimes I need to act in rush. And such kind of monkey things could happen.

Three.js - Collision Detection - Obj Wavefront

I am trying to build a virtual tour inside a building (the whole building is an obj model) using three.js. Everything loads fine and the library is pretty straightforward. My most critical issue is that I can't implement collision detection with the camera, I tried using rays but I couldn't find a suitable example for my case.
My model load:
var loader = new THREE.OBJMTLLoader();
loader.addEventListener( 'load', function ( event ) {
var newModel = event.content;
newModel.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
} );
scene.add( newModel );
objects.push( newModel );
});
loader.load( 'model/model.obj', 'model/model.mtl' );
The camera creation (I don't know if it is relevant to the issue)
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.set( 0, 25, 0 );
camera.lookAt( 0, 0, 0 );
NOTE: The camera moves inside the model, I don't want to detect collision between two separate obj models, I want to detect collision (and stop the camera from passing through walls) inside one single model.
Any help will be greatly appreciated
Looking at the documentation for Raycaster in Three.js at http://threejs.org/docs/58/#Reference/Core/Raycaster, you can create a ray like Raycaster( origin, direction, near, far ). Perhaps for you this would look something like
var ray = new THREE.Raycaster(camera.position, cameraForwardDirection, camera.position, collisionDistance);
Where cameraForwardDirection is the direction in front of you camera. I think you can get this by doing something like:
var cameraForwardDirection = new THREE.Vector3(0,0,-1).applyMatrix4(camera.matrixWorld);
This should work because the camera points in the negative Z direction (hence the 0,0,-1) and we want to apply the orientation of the camera to this vector. This assumes you are only moving forward. If you wanted to check for collisions in other directions, you could cast rays in other directions.
collisionDistance would be the minimum distance for a collision. You can experiment with this to find what works with respect to the scale of things in your scene.
Once you have cast this ray, you will need to check for intersections. You can use the ray.intersectObject( object, recursive ) method. Since it seems like you just have that one model, it might look something like:
var intersects = ray.intersectObject(newModel, true);
if(intersects.length>0){
// stop the camera from moving farther into the wall
}

ray.interstectObjects not intersecting correctly after geometry is dynamically modified

My ray.intersectObjects works really well with the objects in my scene until I dynamically modify the geometry of the object. Although the renderer is showing the objects as being modified (vertices moved and faces changed), when an intersect is tried on the modified object, it produces strange results. I need the intersect to work even on the modified geometry!
To help debug and track how the intersect is working in my scene, I've added a function: makeMiniSphere(). This makes a new sphere object in the scene at the point where the intersection occurs. Using this, you can see that after the cube is modified, sometimes the intersect hits the cube and sometimes it goes right through (mostly the faces that have been modified). It isn't a random problem, but the more you click around the modified cube, the more you can see a pattern develop. It is almost as if the renderer for the visuals of the scene know which direction the cube was modified, but the ray.intersectObjects thinks that it has been modified in a different direction!
Here is a link to the test website: http://www.littledrop.net/html/cadiverse/HelloWorld.html
Directions to show problem:
Left click on cube to show intersect points. Mini spheres will be created wherever Three.js sees an intersect. The color of the selected object will change to yellow if not already selected.
Click on any face of the cube. This will A. Turn it yellow if it isn't already yellow. B. It will select the face of the cube, although the selected face won't look any different from the rest of the cube.
Press the "Right" arrow key to move the selected face to the right. This will dynamically change the geometry of the cube.
Now try to click on the cube--especially in the area that it has been modified. Again the Mini spheres will show where the software thinks the intersects are occurring.
Here is the intersect code:
function onDocumentMouseDown (event)
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
//event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
document.getElementById('message1').innerHTML = window.innerHeight;
var isinworkingarea = window.innerHeight-menubarh;
if (event.clientY<=isinworkingarea)
{
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
//var ray = new THREE.ReusableRay();
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
// use this to select anything in the scene:
var intersects = ray.intersectObjects( scene.children );
if ( intersects.length > 0 )
{
if (cadjectNow)
cadjects[cadjectNow].material.color.setHex(cadjects[cadjectNow].oldColor);
if (intersects[0].object.cadNum)
cadjectNow = intersects[0].object.cadNum;
SELECTEDface=intersects[0].face;
if (cadjectNow)
cadjects[cadjectNow].material.color.setHex( selectColor );
document.getElementById('message1').innerHTML = cadjects[cadjectNum].cadNum;
///// Information about selected /////
var facestring = intersects[0].face.a + " " + intersects[0].face.b + " " + intersects[0].face.c;
if(intersects[0].face instanceof THREE.Face4)
{
facestring=facestring + " " + intersects[0].face.d;
}
copyGeometry=cadjects[cadjectNow].geometry;
//makeCopy(copyGeometry,cadjects[cadjectNow].position.x,cadjects[cadjectNow].position.y,cadjects[cadjectNow].position.z);
makeMiniSphere(intersects[0].point.x, intersects[0].point.y, intersects[0].point.z);
document.getElementById('message1').innerHTML = facestring;
//document.getElementById('message2').innerHTML = cadjects[cadjectNow].geometry.vertices[SELECTEDface.a].x + " " + cadjects[cadjectNow].geometry.vertices[intersects[0].face.a].y + " " + cadjects[cadjectNow].geometry.vertices[intersects[0].face.a].z;
document.getElementById('message2').innerHTML = intersects[0].point.x + " " + intersects[0].point.y + " " + intersects[0].point.z;
}
}
}
Here is the modify code:
if ( keyboard.pressed("right"))
{
document.getElementById('message1').innerHTML = mouseMode;
cadjects[cadjectNow].geometry.vertices[SELECTEDface.a].x+=10;
cadjects[cadjectNow].geometry.vertices[SELECTEDface.b].x+=10;
cadjects[cadjectNow].geometry.vertices[SELECTEDface.c].x+=10;
if(SELECTEDface instanceof THREE.Face4)
{
cadjects[cadjectNow].geometry.vertices[SELECTEDface.d].x+=10;
}
cadjects[cadjectNow].geometry.verticesNeedUpdate = true;
cadjects[cadjectNow].geometry.elementsNeedUpdate = true;
cadjects[cadjectNow].geometry.normalsNeedUpdate = true;
}
Thank you to everyone who has posted past questions and given the answers. By perusing past questions, I've been able to get this far---so you guys have already been a great help. Thanks in advance for help on this one. (As this is my first question to post here, any suggestions on how to better present a question are also more than welcome.)
Update (3/21/13)--I've migrated to r57 as suggested and the updated code is shown above. I've also debugged it so that it is working at least as well as it was before. So now the geometry is still visually being changed dynamically, but the intersect is not detecting the change properly. Thanks #WestLangley for the encouraging posts so far.
Now working correctly. Here is how I did it (thanks to Westlangley for guidance).
Upgrade Three.js to latest revision (r57 from r49).
Migrate my present code to work with r57.
Remove all former code attempting to update the object.
Added the following code to "modify object" section:
cadjects[cadjectNow].geometry.verticesNeedUpdate = true;
cadjects[cadjectNow].geometry.normalsNeedUpdate = true;
cadjects[cadjectNow].geometry.computeFaceNormals();
cadjects[cadjectNow].geometry.computeVertexNormals();
cadjects[cadjectNow].geometry.computeBoundingSphere();
I was having a similar issue with a modified BufferGeometry.
I got raycasting working by calling geometry.computeBoundingSphere() and geometry.computeBoundingBox() after modifying the array with vertex positions.
Setting verticesNeedUpdate was not needed.
Running r112.

three.js and TrackballControls - keeping horizon flat

I'm attempting to modify TrackballControls.js so that its rotation is like that of OrbitControls.js, where the horizon stays flat, but maintain the ability to rotate over and around a scene (specifically, a collada building model). I've been trying to figure this out for the better part of a day now, but I'm a designer, not a programmer. :-) I'm not even sure if I should be focusing on this.rotateCamera and/or this.update.
(BTW, I would just use OrbitControls.js, but it doesn't support panning, which is necessary when looking at large collada building models.)
Any help would be much appreciated.
It's been awhile since this question was asked, but I ran into the same problem and didn't find much discussion online, so I thought I'd post my solution.
If you must use TrackballControls and you want to a flat horizon, you can simply edit the TrackballControls.js library by adding the following line to the end of the 'this.rotateCamera' method
this.object.up = new THREE.Vector3(0,1,0);
This locks the camera up direction in the (0,1,0) direction (i.e in the y direction). The entire modified method function would then read:
this.rotateCamera = function () {
var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
if ( angle ) {
var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize();
quaternion = new THREE.Quaternion();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, -angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_rotateEnd.applyQuaternion( quaternion );
if ( _this.staticMoving ) {
_rotateStart.copy( _rotateEnd );
} else {
quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
_rotateStart.applyQuaternion( quaternion );
}
}
// Lock the camera up direction
this.object.up = new THREE.Vector3(0,1,0);
};

Resources