Merging textured meshes in Three.js - javascript

I'm trying to create multiple meshes, and then merge them into one (with Three.js r.71). Each mesh can have different materials on different faces. I need to get all the materials appear correctly on the resulting mesh.
I found a way to achieve the desired result, but my code looks pretty awful now. Are the developers of three.js really kept in mind this method?
I could not find a suitable example. Can anyone show a better way to do this?
var totalGeom = new THREE.Geometry();
var meshes = [getBlock(), getBlock()];
meshes[0].position.x = 1;
for (var i = 0; i < meshes.length; i++) {
var mesh = meshes[i];
totalGeom.mergeMesh(mesh);
for (var j = mesh.geometry.faces.length-1; j <= 0; j--) {
totalGeom.faces[j].materialIndex = mesh.geometry.faces[j].materialIndex;
}
}
var materials = meshes[0].material.materials.concat(meshes[1].material.materials);
var totalMesh = new THREE.Mesh(totalGeom, new THREE.MeshFaceMaterial(materials));
scene.add(totalMesh);
function getBlock() {
var geometry = new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);
var material = new THREE.MeshFaceMaterial([
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/1.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/3.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')})
]);
return new THREE.Mesh(geometry, material);
}

I would suggest you that instead of mixing all the meshes inside one, you create a group containing all the different meshes. If you do that, you don't need to mix all the materials and you avoid a lot of problems there. And, of course, you get all the advantges of having all the meshes on the same object.
As an example, to do so, follow this procedure:
var scene = new THREE.Scene();
var group = new THREE.Object3D();
var numObjects = 5; //As an example
for(var i=0;i<numObjects;i++){
var cubeGeometry = new THREE.BoxGeometry(100, 100, 100);
var material = new THREE.MeshPhongMaterial();
var mesh = new THREE.Mesh(cubeGeometry, material);
group.add(mesh);
}
scene.add(group);

Related

Merging geometries in three.js

I have a scene which contains multiple meshes, each of varying shapes and sizes.
I have looped through each Mesh and using geometry.merge() I have been able to create a new mesh from the geometries in the scene.
I want to mask the entire mesh with an alphaMask, however, each geometry has the material applied to it separately.
An example of this can be seen here - https://codepen.io/danlong/pen/KXOObr
function addObjects(scene) {
// merged geomoetry & material
var mergedGeometry = new THREE.Geometry();
var mergedMaterial = new THREE.MeshStandardMaterial({ color: "#444", transparent: true, side: THREE.DoubleSide, alphaTest: 0.5, opacity: 1, roughness: 1 });
// multiple meshes
var geometry = new THREE.IcosahedronGeometry(30, 5);
var material = new THREE.MeshStandardMaterial({ color: "#444" });
var geo1 = new THREE.IcosahedronGeometry(30, 5);
var mesh1 = new THREE.Mesh( geo1, material );
mesh1.position.x = 10;
mesh1.position.y = 10;
mesh1.position.z = 0;
var geo2 = new THREE.IcosahedronGeometry(30, 5);
var mesh2 = new THREE.Mesh( geo2, material );
mesh2.position.x = 20;
mesh2.position.y = 20;
mesh2.position.z = 0;
var geo3 = new THREE.IcosahedronGeometry(30, 5);
var mesh3 = new THREE.Mesh( geo3, material );
mesh3.position.x = 30;
mesh3.position.y = 30;
mesh3.position.z = 0;
// scene.add(mesh1, mesh2, mesh3);
mesh1.updateMatrix();
mergedGeometry.merge(mesh1.geometry, mesh1.matrix);
mesh2.updateMatrix();
mergedGeometry.merge(mesh2.geometry, mesh2.matrix);
mesh3.updateMatrix();
mergedGeometry.merge(mesh3.geometry, mesh3.matrix);
// alpha texture
var image = document.createElement('img');
var alphaMap = new THREE.Texture(image);
image.onload = function() {
alphaMap.needsUpdate = true;
};
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGUlEQVQoU2NkYGD4z4AHMP7//x+/gmFhAgCXphP14bko/wAAAABJRU5ErkJggg==';
mergedMaterial.alphaMap = alphaMap;
mergedMaterial.alphaMap.magFilter = THREE.NearestFilter;
mergedMaterial.alphaMap.wrapT = THREE.RepeatWrapping;
mergedMaterial.alphaMap.repeat.y = 1;
// merged geometry with alpha mask
merge1 = new THREE.Mesh(mergedGeometry, mergedMaterial);
merge1.rotation.z = -Math.PI/4;
// merge geometry without alpha mask
var merge2 = new THREE.Mesh(mergedGeometry, material);
merge2.position.x = -100;
merge2.rotation.z = -Math.PI/4;
scene.add(merge1, merge2);
return mesh;
}
The mesh on the left is the merged geometries which I want to apply the alphaMask to. The mesh on the right is the outcome of this and instead of the map being applied to the mesh as a whole, each of the geometries has the map applied.
Is there a way to mask the entire mesh and not each geometry?
--
three.js r86
EDIT:
I've tried to apply a clipping plane to my mesh but it's not the effect I'm looking for. I want to be able to apply an alphaMask across the whole mesh and reveal it however I make my mask image. Something like this effect - https://codepen.io/supah/pen/zwJxdb
Is it something to do with the UV's being preserved from the original geometries? Do I need to change these in some way?
I think what you really want is an overlaid mask. This can be accomplished by rendering a single plane that has the alpha map applied, on top of the scene rendering. Using an orthographic camera, and controlling certain renderer settings, such as disabling automatic clearing of color.

Numerous scenes with numerous meshes out of 1 array

I'm creating a mesh per json object stored in an array containing 100 of them, so I want to add 100 meshes to the scene, and I've got plans to even add to that later on.
This puts an enormous strain on my project in terms of performance, as you could imagine. So, I'm trying to figure out how to cut this array up in 10 manageable parts, and have 10 of these "Planet" objects be added per scene. So there'd be 10 scenes with each scene containing 10 meshes.
I have no idea if this is even doable since this is my first work on three.js. How do I go about it? My code:
var scene1 = new THREE.Scene();
var scene2 = new THREE.Scene();
var scene3 = new THREE.Scene();
var scene4 = new THREE.Scene();
var scene5 = new THREE.Scene();
var scene6 = new THREE.Scene();
var scene7 = new THREE.Scene();
var scene8 = new THREE.Scene();
var scene9 = new THREE.Scene();
var scene10 = new THREE.Scene();
var data = [{
"Planet": "1",
}, {
"Planet": "2",
}, {
"Planet": "3",
}
// this continues up until planet 100
];
//this adds 100 meshes to the scene
for (var i = 0; i < data.length; i++) {
var loader = new THREE.TextureLoader();
var material = new THREE.MeshPhongMaterial({ map: loader.load('image.jpg') });
var geometry = new THREE.SphereGeometry(30, 50, 50);
mesh = new THREE.Mesh(geometry, material);
//random x and z positions so they don't spawn on same spot
mesh.position.x = THREE.Math.randInt(-500, 500);
mesh.position.z = THREE.Math.randInt(-500, 500);
scene1.add(mesh);
}
Edit:
My apologies for not being clear enough as I should have been. The main goal is actually having the functionality of switching through 10 scenes, and seeing 10 "planets" in each scene based on the data array. I've written this code so that it's up to the user to determine which scene is to be rendered and which aren't. Rendering and seeing 100 planets in one scene isn't just horrible for the performance, it's not what my project needs to look like.
var userInput = 1;
if (userInput === 1) {
renderer.render(scene1, camera);
} else {
renderer.render(eval('scene' + userInput), camera);
}
To answer your specific question, you would probably start by storing the scenes in an array:
// Setup scenes
var scenes = [];
for(var i=0;i<10;i++) {
scenes.push( new THREE.Scene() );
}
Then assign the meshes to a scene based on your counter:
//this adds 100 meshes to the scene
for (var i = 0; i < data.length; i++) {
...
var sceneIndex = Math.floor(i/10);
var curScene = scenes[sceneIndex];
curScene.add(mesh);
}
But I'm not sure what your goal is here in terms of improving performance. Is there more code involved than what you've shown? If you still plan on rendering all 10 scenes simultaneously, this won't be any faster than having a single scene.

merging geometry in three.js is not working

A path is changed dynamically. So,
previous render function was,
(LENGTH, getPosition(), RADIUS, MATERIAL, SCENE are already set)
var prevPosition = getPosition();
for(var i =0; i < LENGTH; i++) {
drawPath(getPosition());
}
function drawPath(currentPosition) {
var spline = new THREE.CatmullRomCurve3([prevPosition, currentPosition]);
var geometry = new THREE.TubeGeometry(spline, 1, RADIUS);
var mesh = new THREE.Mesh(geometry, MATERIAL);
SCENE.add(mesh);
}
previous render method works very well.
I changed this for performance.
modified render function is,
var mergedGeometry = new THREE.Geometry();
function drawPath(currentPosition) {
var spline = new THREE.CatmullRomCurve3([prevPosition, currentPosition]);
var geometry = new THREE.TubeGeometry(spline, 1, RADIUS);
mergedGeometry.merge(geometry);
var mesh = new THREE.Mesh(mergedGeometry, MATERIAL);
SCENE.add(mesh);
}
mesh is not displayed.
I don't know why.
Do you know why does not this work?
Please help me.
I believe you've confused geometry for a mesh. Geometry is a component of a mesh. You want to add to your geometry, cast it into a new mesh object, and add that new mesh to the scene.
Try:
var mergedGeometry = new THREE.Geometry();
var prevPosition = getPosition();
for(var i =0; i < LENGTH; i++) {
drawPath(getPosition());
}
var mesh = new THREE.Mesh(mergedGeometry, MATERIAL);
SCENE.add(mesh);
function drawPath(currentPosition) {
var spline = new THREE.CatmullRomCurve3([prevPosition, currentPosition]);
var geometry = new THREE.TubeGeometry(spline, 1, RADIUS);
mergedGeometry.merge(geometry);
}
If you get stuck, there is a functioning mesh merge fiddle here: http://jsfiddle.net/mt2zbb9k/

Rotation around object using Three.js hierarchy (THREE.Group())

So I have the following javascript code:
var group = new THREE.Group();
var staffGeometry = new THREE.BoxGeometry(0.5, 6, 0.5);
var staffMaterial = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
var staffCube = new THREE.Mesh( staffGeometry, staffMaterial );
staffCube.position.x = -21;
staffCube.position.y = 1;
staffCube.position.z = -19;
group.add(staffCube);
//scene.add( staffCube );
var geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 );
var material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
var torusKnot = new THREE.Mesh( geometry, material );
torusKnot.position.x = -21;
torusKnot.position.y = 4.8;
torusKnot.position.z = -19;
torusKnot.scale.set(0.08, 0.08, 0.08);
group.add(torusKnot);
//scene.add( torusKnot );
var ballGeometry1 = new THREE.IcosahedronGeometry(0.5);
var ballMaterial1 = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
var ball1 = new THREE.Mesh(ballGeometry1, ballMaterial1);
ball1.position.x = -22.5;
ball1.position.y = 4.5;
ball1.position.z = -20.5;
group.add(ball1);
//scene.add(ball1);
var ballGeometry2 = new THREE.IcosahedronGeometry(0.5);
var ballMaterial2 = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
var ball2 = new THREE.Mesh(ballGeometry2, ballMaterial2);
ball2.position.x = -22;
ball2.position.y = 6.5;
ball2.position.z = -20;
group.add(ball2);
//scene.add(ball2);
scene.add(group);
The THREE.Group() was an attempt to utilize hierarchy. I can't seem to find ANY useful examples or documentation on THREE.Group so not even sure if I'm using hierarchy correctly. My goal was to set the staff in place, and make it easier to do stuff with the other components through hierarchy.
Mainly, I want ball1 and ball2 to circle around the staff. I am not sure how to access them within the group so that they can use taurusKnots location (head of staff) to translate to, rotate on taurusKnots Y, then translate back.
Any help on utilizing hierarchy with three.js to achieve this would be awesome! Like I said, all I can find online are people saying to use Group() for hierarchy, but nothing is said or can be found (by me at least) on how to access members and actually utilize the hierarchy you establish.

Multiple mesh with one geometry and different textures. Error

I have a loop where I create a multiple Mesh with different geometry, because each mesh has one texture:
var geoCube = new THREE.CubeGeometry(voxelSize, voxelSize, voxelSize);
var geometry = new THREE.Geometry();
for( var i = 0; i < voxels.length; i++ ){
var voxel = voxels[i];
var object;
color = voxel.color;
texture = almacen.textPlaneTexture(voxel.texto,color,voxelSize);
//Return the texture with a color and a text for each face of the geometry
material = new THREE.MeshBasicMaterial({ map: texture });
object = new THREE.Mesh(geoCube, material);
THREE.GeometryUtils.merge( geometry, object );
}
//Add geometry merged at scene
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
mesh.geometry.computeTangents();
scene.add( mesh );
But now I have this error in the javascript code Three.js
Uncaught TypeError: Cannot read property 'map' of undefined
In the function:
function bufferGuessUVType ( material ) {
}
Update:
Finally I have removed the merged solution and I can use an unique geometry for the all voxels. Altough I think that If I use merge meshes the app would have a better performance.
For r53+ the code should be something like this:
var geoCube = new THREE.CubeGeometry(voxelSize, voxelSize, voxelSize);
var geometry = new THREE.Geometry();
var materials = [];
for( var i = 0; i < voxels.length; i++ ){
for ( var j = 0; j < geoCube.faces.length; j ++ ) {
geoCube.faces[ j ].materialIndex = i;
}
var object = new THREE.Mesh( geoCube );
// here I assume you'll me positioning the object.
THREE.GeometryUtils.merge( geometry, object );
var voxel = voxels[i];
var texture = almacen.textPlaneTexture(voxel.texto,voxel.color,voxelSize);
materials.push( new THREE.MeshBasicMaterial( { map: texture } ) );
}
// Add geometry to scene
var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( materials ) );
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
scene.add( mesh );
Some methods are expecting the materials array to be in the mesh face material. For example:
function getBufferMaterial( object, geometryGroup ) {
return object.material instanceof THREE.MeshFaceMaterial
? object.material.materials[ geometryGroup.materialIndex ]
: object.material;
};
Its possible to handle this by referencing the materials array both in the geometry and in the mesh face material. So the line where the mesh is created would look something like:
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(geometry.materials) );
and in the loop processing the voxels:
geometry.materials.push( new THREE.MeshBasicMaterial({ map: texture }));
Each face in the geometry also needs to have a materialIndex assigned or there will be an error. Looping through geometry.faces[] and assigning a materialIndex might be a good way if you know how many faces each of the voxels has.

Categories

Resources