Threejs: Applying material depending on an attribute on the BufferrGeometry - three.js

I know it is possible to assign multiple materials depending on some values on the Face3 object, like:
var materialA = new THREE.MeshLambertMaterial({color: 0x00ccee});
var materialB = new THREE.MeshLambertMaterial({color: 0xFF0000, map: buildingTexture});
var materials = [materialA, materialB];
geometry.materials = [materialA, materialB];
assignUVs(geometry);
for(var i = 0; i< geometry.faces.length; i++){
if(geometry.faces[i].normal.y >= 0.99){
geometry.faces[i].materialIndex = 0;
}else{
geometry.faces[i].materialIndex = 1;
}
}
var object = new THREE.Mesh( geometry, materials );
But let's say that I've an attribute per vertex (fulfilled with the correct value while I was creating the geometry) on a BufferGeometry instance,like:
geometry.addAttribute( 'isLeaf', new THREE.BufferAttribute(palmBuffers.isLeaf,1));
is it possible to apply a material depending on that attribute?
I know that an easier way would be to create a buffer that contains value per face, and not per vertex, I just wanted to know if there is a way to do it starting from per vertex attributes.

The solution that I've implemented is to create a buffer with the same dimension of the number of the faces for that geometry. Then I fullfill the buffer with the value that i need, then I use the values in the buffers to decide whic material to apply for every face.

Related

Is it possible to use a texture material on objects with various sizes?

Working with Three.js r113, I'm creating walls from coordinates of a blueprint dynamically as custom geometries. I've set up the vertices, faces and faceVertexUvs already successfully. Now I'd like to wrap these geometries with a textured material, that repeats the texture and keeps the original aspect ratio.
Since the walls have different lengths, I was wondering which is the best approach to do this?
What I've tried so far is loading the texture once and then using different texture.repeat values, depending on the wall length:
let textures = function() {
let wall_brick = new THREE.TextureLoader().load('../textures/light_brick.jpg');
return {wall_brick};
}();
function makeTextureMaterial(texture, length, height) {
const scale = 2;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( length * scale, height * scale );
return new THREE.MeshStandardMaterial({map: texture});
}
I then call the above function, after creating the geometry and assign the returned materials to the material array to apply it to faces of front and back of each wall. Note: material.wall is an untextured MeshStandardMaterial for the other faces.
let scaledMaterial = [
makeTextureMaterial(textures.wall_brick, this.length.back, this.height),
makeTextureMaterial(textures.wall_brick, this.length.front, this.height),
material.wall
];
this.geometry.faces[0].materialIndex = 0; // back
this.geometry.faces[1].materialIndex = 0; // back
this.geometry.faces[2].materialIndex = 1; // front
this.geometry.faces[3].materialIndex = 1; // front
this.geometry.faces[4].materialIndex = 2;
this.geometry.faces[5].materialIndex = 2;
this.geometry.faces[6].materialIndex = 2;
this.geometry.faces[7].materialIndex = 2;
this.geometry.faces[8].materialIndex = 2;
this.geometry.faces[9].materialIndex = 2;
this.geometry.faces[10].materialIndex = 2;
this.geometry.faces[11].materialIndex = 2; // will do those with a loop later on :)
this.mesh = new THREE.Mesh(this.geometry, scaledMaterial);
What happens is that the texture is displayed on the desired faces, but it's not scaled individually by this.length.back and this.length.front
Any ideas how to do this? Thank you.
I have just found the proper approach to this. The individual scaling is done via faceVertexUvs, as West Langley answered here: https://stackoverflow.com/a/27098476/4355114

Update THREE.TubeGeometry path on the fly

I want to know if it's possible to update the path of a THREE.tubeGeometry after it's created
The basic code is:
var extrudeBend = new THREE.SplineCurve3([
// Initial Vector and Final Vector
new THREE.Vector3(-0.043019063220379364, -0.7286175255425879, 0.32197394147509184),
new THREE.Vector3(-0.21509537768074327, -0.7286180853596855, 2.5424840106551216)]);
var material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: false });
var segments = 40;
var radius = 0;
var radiusSegments = 18;
var closed = false;
var tubeGeometry = new THREE.TubeGeometry(extrudeBend, segments, radius, radiusSegments, closed);
var tubeMesh = new THREE.Mesh(tubeGeometry, new THREE.MeshLambertMaterial({
color: 0xff00ff,
wireframe: true
}));
scene.add(tubeMesh);
For operative reasons, I need to modify those initial and final coordinates by different ones and refresh the entire tube again on the fly.
Modify the mesh vertext is not what I want.
tubeMesh.geometry.vertices[0].set(geoBoxPose.position.x, geoBoxPose.position.y, geoBoxPose.position.z).multiplyScalar(6);
It only modifies one vertex and I want all the tube.
Deleting the tubeGeometry and create it from zero it's a lot of memory consuming, and it is a process that is referenced every little seconds, with many geometries.
Anybody knows how to do that?
You can't change these parameters without re-creating the geometry. To turn those initial and final coordinates into the shape of a spline tube is a complicated mathematical procedure, which is carried out when you create the new geometry. So that step can't be skipped.
In the highly restricted case that your spline curves are all rotations of the original one, you can just rotate the mesh, which is much faster.

Three.js: mapping two textures to a cube

It's tough to find an updated answer to this question. Seems like a lot of solutions are using MeshFaceMaterial which is long gone from three.js (around version 53, currently on 84)
From the research I've done, it seems like it's a better strategy to create a Box, turn it into a Sphere and then do the mappings.
Here's what I've got:
I have textures loaded at vars t1 and t2
var geometry = new THREE.BoxGeometry(1,1,1);
for (var i in geometry.vertices) {
var vertex = geometry.vertices[i];
vertex.normalize().multiplyScalar(1);
}
var mat1 = new THREE.MeshLambertMaterial({ map: t1 });
var mat2 = new THREE.MeshLambertMaterial({ map: t2 });
var materials = [mat1, mat2];
var mats = new THREE.MultiMaterial(materials);
var cube = new THREE.Mesh(geometry, mats);
scene.add(cube);
Problems:
This is resulting in Uncaught TypeError: Cannot read property 'visible' of undefined in my console
For shading reasons, I'd like to use a MeshPhongMaterial instead of a MeshLambertMaterial
Any tips would be hugely appreciated!
Take a look at the materialIndex value inside the faces of the geometry. These values are indexes into your materials array.
In your example you will see they have values from 0 to 5 (corresponding to sides of the box), but since only two materials are provided then you'll see an error.
To solve your problem you can just change the materialIndex values on the geometry faces so that they just reference your two materials.
For example:
for (var i = 0; i < geometry.faces.length; i += 1) {
var face = geometry.faces[i];
face.materialIndex = face.materialIndex % 2;
}

Combining multiple line geometries into a single geometry

I am completely new to Three.js and I am try to add edges to solid models.
The problem is if I add the edges individually the rendering becomes slow. So thinking of combining the geometries into a single so that the rendering speeds up a bit.
I came across this : https://github.com/mrdoob/three.js/issues/1370
But the output is not remaining correct after using above technique.
My code so far below:
/* Edge Data */
var vertices = edgeData.vertices;
var edges = edgeData.edges;
// Final Geometry
var combinedGeo = new THREE.Geometry();
/* Add lines */
for( var i=0; i<edges.length; i++){
var geom = new THREE.Geometry();
for (var j=0; j<edges[i].length; j++){
var v1 = vertices[edges[i][j]];
geom.vertices.push(new THREE.Vector3(v1[0], v1[1], v1[2]));
}
// var line = new THREE.Line(geom, material, THREE.LinePieces);
THREE.GeometryUtils.merge( combinedGeo, geom);
// scene.add(line);
}
var edgesGeo = new THREE.Line(combinedGeo, material, THREE.LineStrip);
scene.add(edgesGeo);
No merging is required. Add pairs of points to your geometry first, and then create one Line with the LinePieces setting. See the THREE.AxisHelper code, for example.

Changing material color on a merged mesh with three js

Is that possible to interact with the buffer used when merging multiple mesh for changing color on the selected individual mesh ?
It's easy to do such thing with a collection of mesh but what about a merged mesh with multiple different material ?
#hgates, your last comment was very helpful to me, I was looking for the same thing for days !
Ok i set on each face a color, and set to true vertexColor on the
material, that solve the problem ! :)
I write here the whole concept that I used in order to add a proper answer for those who are in the same situation :
// Define a main Geometry used for the final mesh
var mainGeometry = new THREE.Geometry();
// Create a Geometry, a Material and a Mesh shared by all the shapes you want to merge together (here I did 1000 cubes)
var cubeGeometry = new THREE.CubeGeometry( 1, 1, 1 );
var cubeMaterial = new THREE.MeshBasicMaterial({vertexColors: true});
var cubeMesh = new THREE.Mesh( cubeGeometry );
var i = 0;
for ( i; i<1000; i++ ) {
// I set the color to the material for each of my cubes individually, which is just random here
cubeMaterial.color.setHex(Math.random() * 0xffffff);
// For each face of the cube, I assign the color
for ( var j = 0; j < cubeGeometry.faces.length; j ++ ) {
cubeGeometry.faces[ j ].color = cubeMaterial.color;
}
// Each cube is merged to the mainGeometry
THREE.GeometryUtils.merge(mainGeometry, cubeMesh);
}
// Then I create my final mesh, composed of the mainGeometry and the cubeMaterial
var finalMesh = new THREE.Mesh( mainGeometry, cubeMaterial );
scene.add( finalMesh );
Hope it will help as it helped me ! :)
Depends on what you mean with "changing colors". Note that after merging, the mesh is like any other non-merged mesh.
If you mean vertex colors, it would be possibly to iterate over the faces and determine the vertices which color to change based on the material index.
If you mean setting a color to the material itself, sure it's possible. Merged meshes can still have multiple materials the same way ordinary meshes do - in MeshFaceMaterial, though if you are merging yourself, you need to pass in a material index offset parameter for each geometry.
this.meshMaterials.push(new THREE.MeshBasicMaterial(
{color:0x00ff00 * Math.random(), side:THREE.DoubleSide}));
for ( var face in geometry.faces ) {
geometry.faces[face].materialIndex = this.meshMaterials.length-1;
}
var mesh = new THREE.Mesh(geometry);
THREE.GeometryUtils.merge(this.globalMesh, mesh);
var mesh = new THREE.Mesh(this.globalMesh, new THREE.MeshFaceMaterial(this.meshMaterials));
Works like a charm, for those who need example but ! This creates mutliple additional buffers (indices and vertex data) , and multiple drawElements call too :(, i inspect the draw call with webgl inpector, before adding the MeshFaceMaterial : 75 call opengl api running at 60fps easily, after : 3490 call opengl api fps drop about 20 % 45-50 fps, this means that drawElements is called for every mesh, we loose the context of merging meshes, did i miss something here ? i want to share different materials on the same buffer

Resources