4

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

Radio
  • 2,612
  • 1
  • 17
  • 40

3 Answers3

4

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);
looshi
  • 1,146
  • 2
  • 8
  • 18
4

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);
}
Radio
  • 2,612
  • 1
  • 17
  • 40
  • How is the performance ? I tried something similar but for about 1000 objects the FPS was very slow using this technique, but I will try again. In my case it would be preferable to retain individual materials as you are doing. – looshi Aug 17 '15 at 16:51
  • 1
    If it's a few geometries, you're ok. E.G. define a plant in a shiny pot: A matte, transparent PNG for the plant and a shiny box planter, then clone the merged mesh at will. My example uses 360k cubes. But only one frame is rendered for inspection and the mesh exported, hence the single mesh. I don't need camera transforms. Adding a material per face on a cube means 6 times the passes on the matrix transformations. You can imagine though 100k cubes would then cost 600k material calcs. Mesh merge has its place but be wise. As you and @gaitat mentioned, there are options for runtime performance. – Radio Aug 17 '15 at 17:30
  • @looshi , forgot to @ you. Please see my reply. – Radio Aug 17 '15 at 17:38
2

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);
gaitat
  • 11,671
  • 3
  • 45
  • 71
  • Well the good news is, I can live without an FPS improvement. I'd like to treat the group as a single mesh object. Is there a reason the materials are not being applied? – Radio Aug 13 '15 at 17:43
  • 1
    As i said, when you merge you will only get one material. But you can treat all those objects as if it was one (regarting transformations) by adding them all to the same group. – gaitat Aug 13 '15 at 17:45
  • Ah, I was going by this answer, which obviously, I see I don't understand: http://stackoverflow.com/questions/27217388/use-multiple-materials-for-merged-geometries-in-three-js – Radio Aug 13 '15 at 17:55
  • I understand you have provided a better avenue for me to treat the meshes as a single group. However, when we do use merge, why supply materials as an array if only one is used? Why does merge have an offset index parameter if only one material is used? Thanks! – Radio Aug 13 '15 at 18:14