Undefined vertexUv on merging BufferGeometries loaded from an .obj file? - three.js

I am loading an .obj file using THREE.OBJLoader with three.js r86.
I would like to merge the children of this object into a single mesh.
Since the loaded geometries of the meshes are BufferGeometry objects, first I transform them into Geometry objects then I perform the merge.
From my understanding there is no way to perform the merge easily directly on the BufferGeometry objects as of yet.
var material = new THREE.MeshBasicMaterial({color: 0xffffff});
var loader = new THREE.OBJLoader( manager );
loader.load( 'Temple-model.obj', function ( object ) {
var geometry = new THREE.Geometry();
for (var i = 0; i < object.children.length; i++) {
var c = object.children[i];
if (c instanceof THREE.Mesh) {
var g = new THREE.Geometry().fromBufferGeometry( c.geometry );
geometry.merge(g);
}
}
var merged = new THREE.Mesh(geometry, material);
scene.add(merged);
});
This seems to work but provokes raises this warning on the line on calling the render function of the renderer.
THREE.DirectGeometry.fromGeometry(): Undefined vertexUv 18483
Why does this happen and what are some better alternatives to achieve this?

Related

THREE.JS : Change material color behind PNG texture ? Assign 2 materials to a mesh imported from GLTF model?

I am trying to change the color of my 3D model "behind" the png texture I set (which includes transparency).
I have done a lot of researches, and i finally found an example with a cube which actually works, but I can't understand how to make that with my gltf 3D model (not a BoxGeometry).
METHOD :
Define an array of two materials,
first one is my png texture with transparency = true;
second one is a basic material with its plain color (the color i will be able to change later...)
var materialBack = new THREE.MeshBasicMaterial({color: 0xfadce6});
var materialTxt = new THREE.MeshBasicMaterial({map: mytexture,transparent: true});
var materials = [materialBack, materialTxt];
It works perfect with a cube :
var geometry = new THREE.BoxBufferGeometry();
geometry.clearGroups();
geometry.addGroup( 0, Infinity, 0 );
geometry.addGroup( 0, Infinity, 1 );
var cube = new THREE.Mesh( geometry, materials );
Problem : I can't figure out how to do the same when my model is actually an imported GLTF, and not a "BoxBufferGeometry". It looks like we can't assign an array to o.material :
var loader = new THREE.GLTFLoader();
loader.load(mymodel.glb, function(gltf) {
gltf.scene.traverse((o) => {
if (o.isMesh) {
o.material = materials;
}
scene.add(gltf.scene);
});
I also tried to extract geometry from gltf, then create a new mesh, but without success :
var loader = new THREE.GLTFLoader();
loader.load(mymodel.glb, function(gltf) {
var geometry = gltf.scene.getObjectByName("name").geometry;
mymesh = new THREE.Mesh(geometry,materials);
scene.add(mymesh);
});
Can someone please help ?

three.js GPU Instantiation with custom mesh and material

Trying to implement GPU instantiation, following the instances/gpu three.js example.
However, somehow nothing seems to be loading in my attempt: http://designs.playgami.com/webgl_loader_fbx3.html
(here is the non-gpu instantiation version - http://designs.playgami.com/webgl_loader_fbx2.1.html)
Here's specifically where I am trying to load the fbx model just once, and then instantiate. I'm trying to instantiate using var object = new THREE.Mesh( geo );, but somehow that does not work?
function CreateCraneScape(texturearray,squareside,armyside){
var total = texturearray.length;
var halfside = Math.floor(squareside*0.5);
loader.load( '/11272018-crane.fbx', function ( geo ) {
var k = 0;
// create cranes
for(var i=-halfside;i<=halfside;i++){
for(var j=-halfside;j<=halfside;j++){
var object = new THREE.Mesh( geo );
CraneApplyTexture(object,texturearray[k]);
CranePosRot(object,i,j);
k++;
}
}
// create army
for(var i=-(halfside+armyside);i<=(halfside+armyside);i++){
for(var j=-(halfside+armyside);j<=(halfside+armyside);j++){
if(j<-halfside||j>halfside || (i<-halfside||i>halfside)){
var object = new THREE.Mesh( geo );
CraneApplyTexture(object,'');
CreatePosRot(object,i,j);
}
}
}
});
}
Apparently FBXLoader does not load just the mesh geometry but actually the entire three.js game object. So, the mesh instantiation needed to reference the geometry of the first child.
geo = geo.children[0].geometry;

Create wireframe of model in threes.js GLTFloader

I want to load a 3D model and create the wireframe in threejs. How may I achieve this effect? Thank you very much.
This is the code I have for now, but it doesn't work.
var loader = new THREE.GLTFLoader();
loader.load('name.gltf', function(geometry, materials) {
var material = new THREE.MeshLambertMaterial();
var mesh = new THREE.Mesh(geometry, material);
group = new THREE.Object3D();
group.add(mesh);
scene.add(group);
});
According to the GLTFLoader docs, the callback's argument is an object with a .scene property containing your model. Like any nested three.js object, you can modify materials using .traverse().
var loader = new THREE.GLTFLoader();
loader.load('name.gltf', function(gltf) {
var object = gltf.scene;
object.traverse((node) => {
if (!node.isMesh) return;
node.material.wireframe = true;
});
scene.add(object);
});
var material = new THREE.MeshLambertMaterial({wireframe:true});

threejs moving one object moves the second too

I'm new at three.js and i need to load a particle object
it seems to work if add particles on vertices
if i load my Json once and inside the load() i build 2 different THREE.Points when i mov the first the second also move if i use 2 different load calls and inside each i build a THREE.Points object each object can be move separately
this i the function that i call inside animate
function animateParticles( particleSystem, particleSystemOriginal, deltaTime ) {
var vertsOriginal = particleSystemOriginal.geometry.vertices;
var verts = particleSystem.geometry.vertices;
for(var i = 0; i < verts.length; i++) {
var vertOriginal = vertsOriginal[i]; // original position
var vert = verts[i]; // cloud position
var vertOriginalY = vertOriginal.y;
var vertY = vert.y;
if (i==1) console.log("vertOriginalY " + vertOriginalY + " vertY " + vertY);
vert.y = vertY - (10 * deltaTime); // move
}
particleSystemOriginal.geometry.verticesNeedUpdate = true;
particleSystem.geometry.verticesNeedUpdate = true;
}
it seems strange that i need to load twice the same object to move one of them
in my function i'just testing and moving dove the cloud but what i would like is to shake particles, so i need to know particles original position and set particles new position
EDIT 1
i load my model like this
var loader = new THREE.JSONLoader();
loader.load('../3d-models/creati/mymodel-001.json', function(geometry, materials) {
var material = new THREE.MeshNormalMaterial();
var particleModelOriginal = new THREE.Mesh( geometry, material );
var particleModel = particleModelOriginal.clone();
/* build particle THREE.Points */
particleSystemOriginal = new THREE.Points(particlesOriginal, particleMaterial);
particleSystem = new THREE.Points(particles, particleMaterial);
//particleSystem = particleSystemOriginal.clone();
});
If I understand the question correctly, you probably need to use the .clone() object on mesh/geometry that you are loading if you want to create multiple objects. Otherwise they are referencing the same object, and will both be modified if one of them are modified

Threejs merge with multiple materials applies only one material

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

Resources