Threejs merge with multiple materials applies only one material - three.js

I have searched on this and found several examples on stackoverflow, but the answers have not solved my problem.
What I have tried:
First I create the geometry bucket to be used for the group and an array to store my materials.
var totalGeom = new THREE.Geometry();
var materials = [];
I then run through my data (strData) with a for loop and call addMapMarker3d.
for(var i=0; i<strData.length;i++){
addMapMarker3d([strData[i].lat,strData[i].lon], strData[i].time, measureColors[i]);
}
The addMapMarker3d function is as follows:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
After the for loop runs and all the cubes are created:
var mats = new THREE.MeshFaceMaterial(materials)
var total = new THREE.Mesh(totalGeom, mats);
world.scene.add(total);
The question
While the geometry merge functions, and my view port is running at a much improved FPS, all the cubes have exactly the same color and opacity. It appears the merge is using a single material of the 10k I supplied. Is there some way to ensure that the geometry uses the material supplied in the array? Am I doing something incorrect?
If I try this in addMapMarker3d:
totalGeom.merge( cube.geometry, cube.matrix, materials.length-1);
I get "Uncaught TypeError: Cannot read property 'transparent' of undefined" and nothing renders, which I don't understand, because by the examples, each geometry should index to a material in the materials array.
three.js r.70

The following technique uses just one material, but allows you to retain the individual color of each merged object. I don't know if it's possible to retain the individual alpha of each merged object.
http://jsfiddle.net/looshi/nsknn53p/61/
For each mesh, set each of its geometry.faces color :
function makeCube(size, color) {
var geom = new THREE.BoxGeometry(size, size, size);
for (var i = 0; i < geom.faces.length; i++) {
face = geom.faces[i];
face.color.setHex(color);
}
var cube = new THREE.Mesh(geom);
return cube;
}
Then, in the parent geometry you are going to mesh into, set its material vertexColors property.
var parentGeometry = new THREE.Geometry();
var parentMatrial = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.SmoothShading,
vertexColors: THREE.VertexColors
});
// in a loop you could create many objects and merge them
for (var i = 0; i < 1000; i++) {
cube = makeCube(size, color);
cube.position.set(x, y, z);
cube.rotation.set(rotation,rotation,rotation);
cube.updateMatrix()
parentGeometry.merge(cube.geometry, cube.matrix);
}
// after you're done creating objects and merging them, add the parent to the scene
parentMesh = new THREE.Mesh(parentGeometry, parentMatrial);
scene.add(parentMesh);

My original question was not answered: Is there some way to ensure that the geometry uses the material supplied in the array?
The answer is yes. Multiple materials can be applied to a single mesh during merge. After pushing the material to the materials array, the merge will utilize the geometry face material index. The merge will apply multiple materials when an array is supplied to new THREE.MeshFaceMaterial([materialsArray]) as the total mesh is created. This solves the mystery of the syntax. Just because you supply an array of materials does not mean that the merge will use each material in an iterative fashion as the objects are merged as of r71. The faces must inform the merge which material in the material array to use.
I am using this for a non rendered scene, and the final obj is exported. If you need render performance, see one of the other answers for some options.
A simple for loop on the face array on the geometry informs the merge which material to apply:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//set the material index of each face so a merge knows which material to apply
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[i].materialIndex = this.materials.length-1;
}
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}

The only reason you are seeing improved performance is because, after the merge, there is only one material. If you want your scene to have multiple materials you should not merge.
Adding to the same group:
var group = new THREE.Object3D();
group.add (object1);
group.add (object2);
group.add (object3);

Related

Same image texture for merged shape

I have made a closed hemisphere by merging geometries of a hemisphere and a circle. I have a 360degree image for texture. I want the image to be applied as the texture to the combined geometry. Currently it is applying the texture twice: to the hemisphere and the circle separately.
I have seen some answers on editing the UV mapping, but I am not sure how to go about it.
Here is the code.
var loader = new THREE.TextureLoader();
loader.setPath(srcPath);
loader.load("./texture.jpg", function(texture) {
var hemiSphereGeom = new THREE.SphereGeometry(radius, radialSegments, Math.round(radialSegments / 4), 0, Math.PI * 2, 0, Math.PI * 0.5);
var objMaterial = new THREE.MeshPhongMaterial({
map: texture,
shading: THREE.FlatShading
});
objMaterial.side = THREE.BackSide;
var capGeom = new THREE.CircleGeometry(radius, radialSegments);
capGeom.rotateX(Math.PI * 0.5);
var singleGeometry = new THREE.Geometry();
var cap = new THREE.Mesh(capGeom);
var hemiSphere = new THREE.Mesh(hemiSphereGeom);
hemiSphere.updateMatrix();
singleGeometry.merge(hemiSphere.geometry, hemiSphere.matrix);
cap.updateMatrix();
singleGeometry.merge(cap.geometry, cap.matrix);
el.setObject3D('hemisphere',new THREE.Mesh(singleGeometry , objMaterial));
});
It appears that the code is seeing the closed hemisphere as the two separate entities still. I would try a 3D modeling program and make the shape there and loading it into the AFrame code. Then load the texture on the back side of the geometry.

how to get the boundingSphere for a whole scene in three.js?

How to get the bounding sphere for a whole scene in three.js?
I may try to get the bounding sphere for each object and compute the resulting union of them, but I think there may be a more straight forward method.
There are different methods to get a boundingSphere of multiple objects dynamically. You can get first the bounding box of all of them, and then create a sphere of that bounding box... here is a sample fiddle I have shaped on boundingSphere of a boundingBox.
Basically you put all the geometries into a Group, you get the Box3 of the group, and then you do getBoundingSphere from the Box3 and position at the center. Code in the fiddle would be this.
let g = new THREE.Group();
scene.add(g);
for (let i = 0; i < 5; i++) {
// geometry
var geometry = new THREE.BoxGeometry(20, 20, 20);
// material
var material = new THREE.MeshToonMaterial({
color: 0xff0000,
opacity: 0.7,
});
// mesh
mesh = new THREE.Mesh(geometry, material);
mesh.position.set(100 * Math.random(), 100 * Math.random(), 100 * Math.random());
g.add(mesh);
}
//g.updateWorldMatrix(true);
var gridHelper = new THREE.GridHelper(400, 40, 0x0000ff, 0x808080);
gridHelper.position.y = 0;
gridHelper.position.x = 0;
scene.add(gridHelper);
let bbox = new THREE.Box3().setFromObject(g);
let helper = new THREE.Box3Helper(bbox, new THREE.Color(0, 255, 0));
scene.add(helper);
const center = new THREE.Vector3();
bbox.getCenter(center);
let bsphere = bbox.getBoundingSphere(new THREE.Sphere(center));
let m = new THREE.MeshStandardMaterial({
color: 0xffffff,
opacity: 0.3,
transparent: true
});
var geometry = new THREE.SphereGeometry(bsphere.radius, 32, 32);
let sMesh = new THREE.Mesh(geometry, m);
scene.add(sMesh);
sMesh.position.copy(center);
EDITED: If you want to include in the boundingSphere for the scene including the lights (which could get you a huge sphere), just start from let bbox = new THREE.Box3().setFromObject(scene)

Using multiple Textures with geometry.faceVertexUvs

I want to use multiple materials and multiple textures on meshes. I use 3 materials and a geometry.faceVertexUvs with two arrays of Uvs. One arrayof UVs for the first texture and one array of UVs for the second texture. However that doesn't quite work. Three.js always uses the uvs of the first array and geometry.faceVertexUvs[1] is never used. Do I have to put all the uvs in geometry.faceVertexUvs[0] for both textures or does it work differently? And if I have to put all uvs in geometry.faceVertexUvs[0] then I wonder why geometry.faceVertexUvs is an array at all. Explanation would be great. The Three.js documentation is not very detailed there.
here my sample code and tanks guys! Tom
var geometry = new THREE.BoxGeometry(60, 60, 60);
var texture1 = new THREE.TextureLoader().load( "../sons.png" );
var texture2 = new THREE.TextureLoader().load( "../Richard-branson.jpg" );
var material0 = new THREE.MeshBasicMaterial( {color:0xffff00});
var material1 = new THREE.MeshPhongMaterial({map:texture1, shininess:100});
var material2 = new THREE.MeshPhongMaterial({map:texture2, shininess:100});
for(var i=0; i< geometry.faces.length;i++){
geometry.faces[i].materialIndex = i%3;
geometry.faceVertexUvs[0][i][0] = new THREE.Vector2(0,0);
geometry.faceVertexUvs[0][i][1] = new THREE.Vector2(1,0);
geometry.faceVertexUvs[0][i][2] = new THREE.Vector2(0,1);
geometry.faceVertexUvs[1]= [];
geometry.faceVertexUvs[1][i] = [];
geometry.faceVertexUvs[1][i][0] = new THREE.Vector2(1,1);
geometry.faceVertexUvs[1][i][1] = new THREE.Vector2(1,0);
geometry.faceVertexUvs[1][i][2] = new THREE.Vector2(0,1);
}
var mesh = new THREE.Mesh(geometry, [material0, material1, material2]);
view.scene.add(mesh);
view.render();

THREE.JS UV Mapping on ExtrudeGeometry

I need to apply a texture on a ExtrudeGeometry object.
The shape is a circle and the extrude path is composed of 2 vectors :
One for the top.
One for the bottom.
I didn't choose cylinderGeometry because I need to place top/bottom sections of my geometry at precise positions and because the geometry created will not be always purely vertical (like a oblique cylinder for example).
Here is a picture of a section (one top vector, one bottom vector and a shape extruded between these 2 vectors).
and a picture of the texture I'm trying to apply.
All I want to do is to wrap this picture on the vertical sides of my object just one time.
Here is my code :
var biVectors = [ new THREE.Vector3( this.startVector.x, this.startVector.y, this.startVector.z ) , new THREE.Vector3( this.endVector.x, this.endVector.y, this.endVector.z ) ];
var wellSpline = new THREE.SplineCurve3(biVectors);
var extrudeSettings = {
steps : 1,
material: 0,
extrudeMaterial: 1,
extrudePath : wellSpline
};
var pts = [];
for (var i = 0; i <= this.segments; i++) {
var theta = (i / this.segments) * Math.PI * 2;
pts.push( new THREE.Vector3(Math.cos(theta) * this.diameter , Math.sin(theta) * this.diameter, 0) );
}
var shape = new THREE.Shape( pts );
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var texture = THREE.ImageUtils.loadTexture( 'textures/sampleTexture2.jpg' );
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.flipY = false;
var material = new THREE.MeshBasicMaterial( { map: texture } );
var slice = new THREE.Mesh( geometry, material );
var faceNormals = new THREE.FaceNormalsHelper( slice );
console.log("face normals: ", faceNormals);
myCanvas.scene.add( faceNormals );
slice.parentObject = this;
myCanvas.scene.add( slice );
this.object3D = slice;
}
Now, as you can see, the mapping is not correct at all.
I've read a lot of information about this problem the last 3 days. But I'm running out of options as I'm new to THREE.JS.
I think I have to redefine the UV coordinates but I have no clue how to do this.
It seems that wrapping a texture on a cylinder like object is anything but easy in THREE.JS.
Can someone please help me on this issue ?

How do you find the size of a particle in Three.js?

How do you find the size of a particle in Three.js?
With geometrys you can use the bounding box/sphere to calculate its size in three.js units. But a particle has no bounding box and I can't see of anyway to calulate its size.
Does anyone have any suggestions?
It depends what meaning of 'particle' you are dealing with. With a WebGLRenderer you would be referring to a vertex in a ParticleSystem, but with a CanvasRenderer you mean Particle (which is Canvas only). In terms of the ParticleSystem the material used to construct it has a size, like in the usual examples:
geometry = new THREE.Geometry();
for ( i = 0; i < 10000; i ++ ) {
var vertex = new THREE.Vector3(); // and set its location
geometry.vertices.push( vertex );
}
material = new THREE.ParticleBasicMaterial( {
size: 35, // This might be the size you are thinking of?
sizeAttenuation: false,
map: // Some THREE.Texture
});
particles = new THREE.ParticleSystem( geometry, material );
Then you can access the size, and modify it simply with
particles.material.size = newSize;
If you are creating a Particle for canvas, then it is very similar:
// Construct the particle with a material
var material = new THREE.ParticleBasicMaterial( {
map: new THREE.Texture( canvas ),
blending: THREE.AdditiveBlending
});
particle = new THREE.Particle( textMaterial );
// Just need to access the material to get the 'size'
var size = particle.material.size

Resources