Three.js: How to merge two BufferGeometries and keep transforms? - three.js

A non-buffergeometry, Geometry.merge(), takes a matrix to apply to the geometry when merging:
.merge ( geometry, matrix, materialIndexOffset )
However, BufferGeometry.merge() does not take a matrix:
.merge ( bufferGeometry, offset )
I have confirmed that merging two BufferGeomtries together does not preserve transforms of the objects being merged.
Here's the kicker: I would like to do this without converting to a Geometry at any step. I would like to do it all in BufferGeometry land.
Is this possible?

We have been doing sich merging of (mostly single objects) to maintain the transformations for JSON Object exporting.
To bake the transformations we do run a small function
getFixedMesh = function(geometry) {
var fixedmesh = null;
switch (geometry.constructor.name) {
case 'BufferGeometry':
var srcmesh = new THREE.Mesh(geometry);
srcmesh.updateMatrix();
return THREE.BufferGeometryUtils.mergeBufferGeometries([srcmesh.geometry]);
default:
var srcmesh = new THREE.Mesh(geometry);
srcmesh.updateMatrix();
fixedmesh = new THREE.Geometry();
fixedmesh.merge(srcmesh.geometry, srcmesh.matrix);
return fixedmesh;
}
}
This works for many arrangements, but still does some errors on LineSegments (and others?)

BufferGeometry has .applyMatrix ( matrix ) method that you can use to 'bake' object transform into vertex data before merging it.

Related

Meshes of merged geometries are not properly joined

When I merge geometries, then I export in STL, here is the result of mesh I can finally get:
As you can see, the join is not well aligned.
If I export same objects from my 3D software, this is what I get and how it would look like:
Here it is exported as 1 object.
In Threejs, scene is imported from glb.
My code for merged:
function mergeGeometries(clone) {
let geoms=[]
let meshes=[]
clone.updateMatrixWorld(true,true)
clone.traverseVisible(e=>e.isMesh && meshes.push(e) && (geoms.push(( e.geometry.index ) ? e.geometry.toNonIndexed() : e.geometry().clone())))
geoms.forEach((g,i)=>g.applyMatrix4(meshes[i].matrixWorld));
let gg = BufferGeometryUtils.mergeBufferGeometries(geoms,true)
gg.applyMatrix4(clone.matrix.clone().invert());
gg.userData.materials = meshes.map(m=>m.material)
gg.scale(10,10,10)
var mergeMesh = new THREE.Mesh(gg, new THREE. MeshLambertMaterial ({color: 0xff0000}));
return mergeMesh
}
Is there another way or did I miss something to export a combination of objects as one object in STL or OBJ ?

Is there a way I can create a Path or Curve to use for TubeGeomety(path,...) from an existing geometry's points/vertices array?

I'm very new to both three.js & to js in general.
1st I select a polyHedron geometry with a dat.gui checkbox
which renders say a tetrahedron. these selections work.
I also have a dat.gui checkbox to either phongfill or wireframe render.
I initially wanted just a wireframe type mesh but not with all of the internal triangles. I found the edgesgeometry() function which draws pretty much what I want(hard edges only). there is however a known issue with linewidth not working in windows anymore. all lines drawn as strokeweight/width 1.
I'd like to use tubeGeometry() to draw tubes of whatever radius as opposed to 1weight lines. I know I'll have to draw something such as a sphere at/over the connection vertices for it to not look ridiculous.
geo = new THREE.TetrahedronBufferGeometry(controls0.Radius,controls0.Detail);
...
egeo = new THREE.EdgesGeometry( geo );
lmat = new THREE.LineBasicMaterial({ color: 0x0099ff, linewidth: 4 });
ph = new THREE.LineSegments( egeo, lmat );
scene.add(ph);
....
playing around in the console I found some geometry/bufferGeomery arrays that are likely the vertices/indices of my selected X-hedron as their sizes change with type(tetra/icosa etc) selection & detail increase/decrease:
//p = dome.geometry.attributes.uv.array;
p = egeo.attributes.position.array
//p = geo.attributes.uv.array
...
var path = new THREE.Curve();
path.getPoint = function (t) {
// trace the arc as t ranges from 0 to 1
var segment = (0 - Math.PI*2) *t;
return new THREE.Vector3( Math.cos(segment), Math.sin(segment), 0);
};
var geomet = new THREE.TubeBufferGeometry( path, 10, 0.2, 12, false );
var mesh = new THREE.Mesh( geomet, mat );
scene.add( mesh );
from above the tubeGeometry() draws fine separately as well but with the "path" made by that curve example. How can I use the vertices from my tetrahedron for example to create that "path" to pass to tubegeometry() ?
maybe a function that creates "segment vectors" from the vertices ?
I think it needs other properties of curve/path as well ?
I'm quite stuck at this point.
ANY Help, suggestions or examples would be greatly appreciated !
thanks.
You can try to create a TubeGeometry for each edge. Generate a LineCurve3 as the input path. Use the vertices of the edge as the start and end vector for the line.
Consider to use something like "triangulated lines" as an alternative in order to visualize the wireframe of a mesh with a linewidth greater than 1. With the next release of three.js(R91) there are new line primitives for this. Demo:
https://rawgit.com/mrdoob/three.js/dev/examples/webgl_lines_fat.html
This approach is much more performant than drawing a bunch of meshes with a TubeGeometry.

Applying different textures on a THREE.js OBJLoader loaded object

I have a square shape .obj model and 2 textures. How do I apply one texture on it's top face and another on rest of faces?
There are a ton of ways to do what you're asking all with varying complexity depending on your needs. It looks like you want to apply two materials to your object and not two textures.
It looks this way because it seems you want the textures to be interchangeable so there's no way you're going to combine the images and keep resolution and OBJ & THREE.Material only support one set of uv attributes so you can't use a single material and multiple textures. So multiple materials it is...
Multiple materials
If you have two materials (2 THREE.Materials which correlate to 2 WebGL programs) then each face needs to know what material it's assigned to.
While the THREE.js multi material API has been in flux for quite a while and there are differences between THREE.Geometry and THREE.BufferGeometry, fortunately for you THREE.OBJLoader supports material groups out of the box. To get this into THREE.js, you want to apply multiple materials in your 3D editor to your object and then export the OBJ to get everything. Doing it by hand is a little harder and requires calling addGroup as shown in the docs/the link above.
In THREE.js you simply pass in all the materials as an array to your object demonstrated in this answer. I also updated your fiddle to do the same thing. Relevant code shown below
var loadingManager = new THREE.LoadingManager();
var ObjLoader = new THREE.OBJLoader(loadingManager);
var textureLoader = new THREE.TextureLoader(loadingManager);
//Material 1 with first texture
var material = new THREE.MeshLambertMaterial({map: textureLoader.load('https://dl.dropboxusercontent.com/s/nvnacip8fhlrvm4/BoxUV.png?dl=0')});
//Material 2 with second texture
var material2 = new THREE.MeshLambertMaterial({map:
textureLoader.load('https://i.imgur.com/311w7oZ.png')});
ObjLoader.load(
'https://dl.dropboxusercontent.com/s/hiazgei0rxeirr4/cabinet30.obj?dl=0',
function ( object ) {
var geo = object.children[0].geometry;
var mats = [material, material2];
//These are just some random groups to demonstrate multi material, you need to set these up so they actually work for your object, either in code or in your 3D editor
geo.addGroup(0,geo.getAttribute("position").count/2,0);
geo.addGroup(geo.getAttribute("position").count/2,
geo.getAttribute("position").count/2,1);
//Mesh with multiple materials for material parameter
obj = new THREE.Mesh(geo, mats);
obj.position.y = 3;
});

Rigid body (shape) in bullet/ammo.js from a mesh in three.js

I am using bullet/ammo.js with three.js. I have a 3d mesh and I want to use the exact shape for collision detection with a soft body. Is there a way I can create a 3d rigid body (in bullet) from a mesh (in three.js)?
Here is an example:
http://kidzinski.com/miamisura/lazy3d/ (please wait a second for the 3d model to download). I have a cloth falling on a 3d body and I need to simulate collision of this cloth with the body.
I am new to these frameworks sorry if I fundamentally misunderstood something.
It looks like you can do some work to turn an arbitrary Three.js mesh into a Bullet concave mesh. This is supported by Physi.js, which is a plug and play solution to link Three.js directly to ammo.js. I personally wouldn't recommend using the project (Physi.js) but you can look at the source code to see how they implement concave meshes.
First they loop over the geometry to create a custom list of "triangle" data objects on these lines of physi.js
for ( i = 0; i < geometry.faces.length; i++ ) {
face = geometry.faces[i];
if ( face instanceof THREE.Face3) {
triangles.push([
...
Then these triangles are passed off to Ammo.js to make a new Ammo.btBvhTriangleMeshShape on these lines:
for ( i = 0; i < description.triangles.length; i++ ) {
...
triangle_mesh.addTriangle( _vec3_1, _vec3_2, _vec3_3, true );
}
...
shape = new Ammo.btBvhTriangleMeshShape( triangle_mesh, true, true );
This should be a good starting point for building your own Ammo.js custom mesh.
There are lots of threads around the web, that Physijs Concave mesh does not work with collission. It seems, that btBvhTriangleMeshShape is not intended to work with collission in ammo.js, as I found out searching for that topic in bullet related forums.
What worked for me, is btConvexHullShape:
var triangle, triangle_mesh = new Ammo.btTriangleMesh;
var btConvexHullShape = new Ammo.btConvexHullShape();
var _vec3_1 = new Ammo.btVector3(0,0,0);
var _vec3_2 = new Ammo.btVector3(0,0,0);
var _vec3_3 = new Ammo.btVector3(0,0,0);
for ( i = 0; i < triangles.length; i++ ) {
triangle = triangles[i];
_vec3_1.setX(triangle[0].x);
_vec3_1.setY(triangle[0].y);
_vec3_1.setZ(triangle[0].z);
btConvexHullShape.addPoint(_vec3_1,true);
_vec3_2.setX(triangle[1].x);
_vec3_2.setY(triangle[1].y);
_vec3_2.setZ(triangle[1].z);
btConvexHullShape.addPoint(_vec3_2,true);
_vec3_3.setX(triangle[2].x);
_vec3_3.setY(triangle[2].y);
_vec3_3.setZ(triangle[2].z);
btConvexHullShape.addPoint(_vec3_3,true);
triangle_mesh.addTriangle(
_vec3_1,
_vec3_2,
_vec3_3,
true
);
}
return btConvexHullShape;
In the process of learning physic based 3d with threejs, I also want to mention the following best practice: when using complex models, create a low poly model that you can push to that converter function instead of the original model, or you will encounter a stack overflow.

THREE.js Reusing geometry does not seem to work efficiently

I am loading several models into scene using the same geometry like so (pseudo code):
var geoCache = [];
function parseJSONGeometry(json_geo){
// this code is the three.js model parser from the jsonloader
return geometry;
}
function loadCachedGeo(data){
if( !geoCache[data.id] ){
geoCache[json.id] = parseJSONGeometry(data);
}
return geoCache[json.id];
}
function loadObjects(json){
var mats = [];
combined = new THREE.Geometry();
for(i=0<i<json.geometries.length;i++){
data = json.geometries[i];
geo = loadCachedGeo(data.id);
mats.push(new THREE.MeshBasicMaterial(map:THREE.imageUtils.loadTexture(data.src)));
mesh = new THREE.Mesh(geo);
mesh.position.set(data.x,data.y,data.z);
combined = THREE.GeometryUtils.mergeGeometry(combined,mesh);
}
mesh = new THREE.Mesh(combined,new THREE.MeshFaceMaterial(mats));
scene.add(mesh);
}
I also cache the textures, however I omitted that for the sake of simplicity.
When I call:
renderer.info.render.faces
renderer.info.memory.textures
renderer.info.memory.programs
renderer.info.memory.geometries;
renderer.info.render.calls
I notice when one object is on the screen the poly count is say 1000, textures: 1, calls: 1, shaders: 1 and geometries: 1. When two objects are on the screen 2000 faces are reported, 1 texture, 1 shader, 2 calls, and 2 geometries.
I thought that reusing geometry in this fashion only loads the geometry once into the gpu. Am I missing something, can someone PLEASE explain this behavior?
Three.js r59
You need to inspect
renderer.info.memory.geometries
There is also
renderer.info.memory.textures
renderer.info.memory.programs
three.js r.59

Resources