Three.js: mapping two textures to a cube - three.js

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

Related

Raycaster does not find intersect even though it should

I am using a raycaster for a projection of a point onto a face. But somehow that doesn't seem to work. I.e. taking point (25,25,300) and direction (0,0,-1) the raycaster doesn't find and intersect with a box of size (30,30,30) located at (0,0,0). Am I doing sth wrong?
var geometry = new THREE.BoxGeometry(30, 30, 30);
var material = new THREE.MeshBasicMaterial( );
var mesh = new THREE.Mesh(geometry, material);
var dir = new THREE.Vector3(0,0,-1);
var p = new THREE.Vector3(25,25,300);
var raycaster = new THREE.Raycaster(p, dir);
var intersects = raycaster.intersectObjects(mesh); // returns an empty array
There are two problems with your example, first is that you are using the method raycaster.intersectObjects which takes an array as argument, when you should be using raycaster.intersectObject which takes an object.
Secondly, you are missing the mesh.
Try these values: var p = new THREE.Vector3(15,15,300); instead. The image below illustrates the problem..

Threejs: Applying material depending on an attribute on the BufferrGeometry

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.

three.js / physi.js heightfield wont accept geometry

I'm attempting to create a large terrain in three.js, i'm using physi.js as the physics engine.
generating the geometry from the heightmap is no problem so far. however, when i try to add it as a THREE.Mesh it works beautifully, when i try adding it as a Physijs.HeightfieldMesh i get the following error:
TypeError: geometry.vertices[(a + (((this._physijs.ypts - b) - 1) * this._physijs.ypts))] is undefined
The geometry is generated as a plane, then the Z position of each vertex gets modified according to the heightmap.
var geometry = new THREE.PlaneGeometry( img.naturalWidth, img.naturalHeight,img.naturalWidth -1, img.naturalHeight -1 );
var material = new THREE.MeshLambertMaterial( { color : 0x0F0F0F } );
//set height of vertices
for ( var i = 0; i<plane.geometry.vertices.length; i++ ) {
plane.geometry.vertices[i].z = data[i];//let's just assume the data is correct since it works as a THREE.Mesh
}
var terrain = new THREE.Mesh(geometry, material); // works
//does not work
var terrain = new Physijs.heightfieldMesh(
geometry,
material,
0
);
I think your problem is you are using "plane.geometry" instead of just "geometry" in the loop to set the vertex height.
Maybe it should be:
//set height of vertices
for ( var i = 0; i < geometry.vertices.length; i++ ) {
geometry.vertices[i].z = data[i];//let's just assume the data is correct since it works as a THREE.Mesh
}
This fiddle that I created seems to work ok.

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

Applying textures on cube geometry using THREE.js

I have a simple THREE.js app that renders a cube and applies textures on each of the faces like so:
var cubeGeometry = new THREE.CubeGeometry(5, 8, 1, 4, 4, 1);
var materials = [ new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture('front.jpg') }),
//.....Front, back, left, etc...
];
...
var cubeMesh = new THREE.Mesh(cubeGeometry, new THREE.MeshFaceMaterial(materials));
However, all I see is a black cube, i.e. the images dont appear ont the cube's faces.
Also, my code works fine in release 50 of the THREE.js library, so it seems like a change is a newer release has caused my code to break, and I cant seem to find any relavent documentation around it.
Any help is appreciated.
The following code should work as of release 97
// creates cubes geometry in front of camera (assuming your camera position and rotation has not changed)
var geometry = new THREE.CubeGeometry(1, 1, 1, -2, 0, 0);
// creates a texture loader for the cube
var texture = new THREE.TextureLoader();
// defines variables to make things easier
var counter, textures = [], materials = [];
// iterate through all 6 sides of the cube
for(counter = 0; counter < 6; counter ++) {
// loads and stores a texture (you might run into some problems with loading images directly from a source because of security protocols, so copying the image data is a for sure way to get the image to load)
textures[counter] = texture.load('data:image/restOfImageAddress');
// creates material from previously stored texture
materials.push(new THREE.MeshBasicMaterial({map: textures[counter]}));
}
// creates the cube by mixing the geometry and materials
var cubeMesh = new THREE.Mesh(geometry, materials);

Resources