why three.js updates geometry only when new geometry is defined? - three.js

I have a question about updating geometry in three.js using dat.gui.
I expected I could update the geometry by updating the value tied into the geometry by object as below.
gui.add(parameter, 'width')
.min(1)
.max(10)
.step(0.1)
.name('cubeWidth')
.onChange((value) => {
mesh.geometry.dispose();
geometry.parameters.width = parameter.width
console.log(mesh)
})
However, it updates the geometry only when I redefine the geometry like below.
mesh.geometry = new THREE.BoxGeometry(parameter.width, 1, 1);
It's bit weird to me because even when I log the geometry data both shows the updated width value like below.
Why does only the first approach work while the other one not work?

geometry.parameters.width = parameter.width
This has no effect. Parameters are only processed when a geometry is created. The parameters property is in fact read-only.
The solution for your issue is indeed to recreate the geometry with the new set of parameters.

Related

threejs - creating 3d perspective for a line

I'm working on an app where I visualize ATV trails in a 3d perspective (NAIP imagery draped over elevation data). I am using three.js for the rendering engine.
In the above image, the white line you see is just a THREE.Line instance, where I convert a trails gps coordinates into threejs coordinates. I'd like to add more of 3d perspective to this line. I tried implementing a THREE.TubeGeometry where the path was a THREE.CatmullRomCurve3 using the same Vector3 points as how I built the line you see in the image above. That did not produce a desirable result...
From the many, many THREE examples I have looked at, I really think an extruded geometry would achieve the look I am after... But I cant for the life of me figure out how to extrude a geometry for the line. Any suggestions/thoughts?
UPDATE 1:
Here is my desired look (same trail - no imagery). This image was produced in QGIS using the Q2Threejs plugin
UPDATE 2: Here is a code of how I have attempted to create a tubegeometry. Maybe I am messing something up in there...
// trailVectors are an array of Vector3 - same as ones used to create line
var trailCurve = new THREE.CatmullRomCurve3(trailVectors);
var tubeGeometry = new THREE.TubeGeometry(trailCurve,80,1,15,false);
var material = new THREE.MeshBasicMaterial({color:0x00ff00});
var tubeMesh = new THREE.Mesh(tubeGeometry,material);
var wireframeMaterial = new THREE.LineBasicMaterial({color:0xffffff,lineWidth:2});
var wireframe = new THREE.Mesh(tubeGeometry,wireframeMaterial);
tubeMesh.add(wireframe);
scene.add(tubeMesh);
UPDATE 3
THREE.TubeGeometry(trailCurve,80,4,2,false) per mzartman request
I think that you should be able to achieve what you want with a TubeGeometry. I think the big thing is that your example (from the picture shown) has more than 2 radius segments. That gives it the tubular shape and makes it look sort of like a blob. If you set the radial segment count to 2 (as it's shown below) then I think it would look a lot better.
tubeGeometry = new THREE.TubeBufferGeometry(
[YOUR_PATH_HERE],
params.extrusionSegments, // <--- Edit this for higher resolution on the spline
3, // <--- This defines the height
2, // <--- This 2 keeps 2D (i.e. not a tube!!!!)
true );
var mesh = new THREE.Mesh( geometry, material );
var wireframe = new THREE.Mesh( geometry, wireframeMaterial );
mesh.add( wireframe );
scene.add( mesh );
Update:
I think that you might do better with a material that shows some shadow like the MeshPhong. Also, to do the wireframe you want to add it as an option in the material initialization. Give it a show with the following:
var tubeGeometry = new THREE.TubeGeometry(curve,80,1,2,false);
var material = new THREE.MeshPhongMaterial({color:0x00ff00, wireframe: true});
var tubeMesh = new THREE.Mesh(tubeGeometry,material);
scene.add(tubeMesh);

ThreeJS apply properties from one camera to another camera

In my webapp I'm using ThreeJS scenes in different modals/popups/dialogs with different width/height ratios.
Furthermore, I want to use multiple user defined camera settings (rotation, position, lookAt etc.) among these different scenes.
Therefore, I save the camera object via camera.toJSON() when the user clicks a capture camera settings button.
(Before I did this, I saved just the object camera, but unfortunately these objects are quite big and slow down the performance while multiple camera objects get stored. Nevertheless, this approach worked, since I was able to copy all the desired values between the saved camera object and the currently used camera [e.g. current_camera.position.x=saved_camera.position.x and so on])
In every scene I want now to use the saved properties I tried the following:
let m = new THREE.Matrix4();
m.fromArray(saved_camera.object.matrix);
current_camera.applyMatrix(m)
current_camera.updateMatrix();
Unfortunately this doesn't work.
"normal" camera object
camera.toJSON() object
If you're comfortable using matrices, then you can turn off the matrix auto-update that three.js does during the render process, and keep the world matrix up-to-date yourself. (This includes any time you change the camera's orientation, so keep that in mind if you're using some form of mouse interaction to control the camera angle.)
First, turn off automatic matrix updating for your camera by setting the autoUpdateMatrix property to false. You can still use the convenience properties (position, rotation, scale), but you'll have to manually update the world matrix by calling camera.updateMatrixWorld(true);.
Finally, when you're ready to restore a particular camera orientation, simply copy the matrix values using the matrixWorld's copy method.
var origin = new THREE.Vector3();
var theCamera = new THREE.PerspectiveCamera(35, 1, 1, 1000);
theCamera.autoUpdateMatrix = false; // turn off auto-update
theCamera.position.set(10, 10, 10);
theCamera.lookAt(origin);
theCamera.updateMatrixWorld(true); // manually update the matrix!
console.log("Camera original matrix: ", theCamera.matrixWorld.elements.toString());
var saveMatrix = new THREE.Matrix4();
saveMatrix.copy(theCamera.matrixWorld);
// saveMatrix now contains the current value of theCamera.matrixWorld
theCamera.position.set(50, -50, 75);
theCamera.lookAt(origin);
theCamera.updateMatrixWorld(true); // manually update the matrix!
console.log("Camera moved matrix: ", theCamera.matrixWorld.elements.toString());
// theCamera.matrixWorld now holds a value that's different from saveMatrix.
theCamera.matrixWorld.copy(saveMatrix);
// Don't upate the matrix, because you just SET it.
console.log("Camera moved matrix: ", theCamera.matrixWorld.elements.toString());
// theCamera.matrixWorld once again contains the saved value.
<script src="https://threejs.org/build/three.js"></script>
Edit to address OrbitControls:
It looks like OrbitControls uses the convenience properties, rather than gathering the information from the matrix. As such, when you restore a camera position, you'll also need to restore those properties. This is easily done by using decompose on the matrix, and copying the resulting values into the appropriate properties:
var d = new THREE.Vector3(),
q = new THREE.Quaternion(),
s = new THREE.Vector3();
camera.matrixWorld.decompose( d, q, s );
camera.position.copy( d );
camera.quaternion.copy( q );
camera.scale.copy( s );

Applying different textures on a THREE.js OBJLoader loaded object

I have a square shape .obj model and 2 textures. How do I apply one texture on it's top face and another on rest of faces?
There are a ton of ways to do what you're asking all with varying complexity depending on your needs. It looks like you want to apply two materials to your object and not two textures.
It looks this way because it seems you want the textures to be interchangeable so there's no way you're going to combine the images and keep resolution and OBJ & THREE.Material only support one set of uv attributes so you can't use a single material and multiple textures. So multiple materials it is...
Multiple materials
If you have two materials (2 THREE.Materials which correlate to 2 WebGL programs) then each face needs to know what material it's assigned to.
While the THREE.js multi material API has been in flux for quite a while and there are differences between THREE.Geometry and THREE.BufferGeometry, fortunately for you THREE.OBJLoader supports material groups out of the box. To get this into THREE.js, you want to apply multiple materials in your 3D editor to your object and then export the OBJ to get everything. Doing it by hand is a little harder and requires calling addGroup as shown in the docs/the link above.
In THREE.js you simply pass in all the materials as an array to your object demonstrated in this answer. I also updated your fiddle to do the same thing. Relevant code shown below
var loadingManager = new THREE.LoadingManager();
var ObjLoader = new THREE.OBJLoader(loadingManager);
var textureLoader = new THREE.TextureLoader(loadingManager);
//Material 1 with first texture
var material = new THREE.MeshLambertMaterial({map: textureLoader.load('https://dl.dropboxusercontent.com/s/nvnacip8fhlrvm4/BoxUV.png?dl=0')});
//Material 2 with second texture
var material2 = new THREE.MeshLambertMaterial({map:
textureLoader.load('https://i.imgur.com/311w7oZ.png')});
ObjLoader.load(
'https://dl.dropboxusercontent.com/s/hiazgei0rxeirr4/cabinet30.obj?dl=0',
function ( object ) {
var geo = object.children[0].geometry;
var mats = [material, material2];
//These are just some random groups to demonstrate multi material, you need to set these up so they actually work for your object, either in code or in your 3D editor
geo.addGroup(0,geo.getAttribute("position").count/2,0);
geo.addGroup(geo.getAttribute("position").count/2,
geo.getAttribute("position").count/2,1);
//Mesh with multiple materials for material parameter
obj = new THREE.Mesh(geo, mats);
obj.position.y = 3;
});

How to fix tiny holes in the Mesh after subtract operation with Three.CSG

I have a Box Mesh where I subtract another Box with Three.CSG to create a wall with a window.
After doing so, there are tiny holes in the Mesh alongside the cut. They are not visible alle the time, but show up when moving around.
How to close these holes?
This is part of the code how I am creating the Mesh:
var wallBsp = new ThreeBSP( myWallMesh );
var subMesh = new THREE.Mesh( mygeo );
var subBsp = new ThreeBSP( subMesh );
var subtract_bsp = wall_bsp.subtract( subBsp );
var result = subtract_bsp.toMesh();
result.material.shading = THREE.FlatShading;
result.geometry.computeVertexNormals();
Update
I have created a jsfiddle, but it is difficult to reproduce the error, I couldnt make it visible there: http://jsfiddle.net/L0rdzbej/23/
However, you can see the full application here.
Like #gaitat suggested, geometry.mergeVertices() does not look like it changes anything for me. Chandler Prall hinted at the source where precisionPoints, which is a variable inside the mergeVertices function, could solve this. Depending on the scale of the scene its value should be lower or negative, but I had no success so far.

Outline object (normal scale + stencil mask) three.js

For some time, I've been trying to figure out how to do an object selection outline in my game. (So the player can see the object over everything else, on mouse-over)
This is how the result should look:
The solution I would like to use goes like this:
Layer 1: Draw model in regular shading.
Layer 2: Draw a copy in red color, scaled along normals using vertex shader.
Mask: Draw a black/white flat color of the model to use it as a stencil mask for the second layer, to hide insides and show layer 1.
And here comes the problem. I can't really find any good learning materials about masks. Can I subtract the insides from the outline shape? What am I doing wrong?
I can't figure out how to stack my render passes to make the mask work. :(
Here's a jsfiddle demo
renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetParameters)
composer = new THREE.EffectComposer(renderer, renderTarget)
// composer = new THREE.EffectComposer(renderer)
normal = new THREE.RenderPass(scene, camera)
outline = new THREE.RenderPass(outScene, camera)
mask = new THREE.MaskPass(maskScene, camera)
// mask.inverse = true
clearMask = new THREE.ClearMaskPass
copyPass = new THREE.ShaderPass(THREE.CopyShader)
copyPass.renderToScreen = true
composer.addPass(normal)
composer.addPass(outline)
composer.addPass(mask)
composer.addPass(clearMask)
composer.addPass(copyPass)
Also I have no idea whether to use render target or renderer for the source of the composer. :( Should I have the first pass in the composer at all? Why do I need the copy pass? So many questions, I know. But there are just not enough resources to learn from, I've been googling for days.
Thanks for any advice!
Here's a js fiddle with working solution. You're welcome. :)
http://jsfiddle.net/Eskel/g593q/6/
Update with only two render passes (credit to WestLangley):
http://jsfiddle.net/Eskel/g593q/9/
The pieces missing were these:
composer.renderTarget1.stencilBuffer = true
composer.renderTarget2.stencilBuffer = true
outline.clear = false
Now I think I've found a bit simpler solution, from the THREEx library. It pre-scales the mesh so you dont need a realtime shader for it.
http://jeromeetienne.github.io/threex.geometricglow/examples/geometricglowmesh.html

Resources