I've been trying to understand the maths behind Three.JS's skinning and I've been stuck there for a while.
My use case is loading "any" 3D model that is passed through OpenAssetImport and saved in a custom model format (mostly built for a custom native render, but it's just a representation of assimp's data structures).
In this model format I track:
Meshes (obviously)
Nodes (as in the 'assimp' definition of nodes)
Bones
For each bone, I track:
The node which it is attached to
The individual 'bind matrix' as defined by assimp
I'm then struggling to re-construct the skeleton in Three.JS. My attempt is this:
Generate all the 'nodes' (as in, 'assimp' nodes) as simple THREE.Object3D
Attach the meshes when required
For each bone
Find the node (THREE.Object3D) it's attached to
Create a new THREE.Bone
Apply assimp's 'bind matrix' to the ThreeJS bone with bone.applyMatrix(assimpBone.bindMatrix)
Set the THREE.Bone as a child of the THREE.Object3D
Create a THREE.Skeleton with the array of THREE.Bone objects
Attach it to the mesh with 'attached' bind mode
However, for some models this works, while for others I have big errors with meshes being de-formed (even if the base mesh is still at least recognizable).
Example mesh loaded with skinning disabled:
Same mesh loaded with skinning enabled:
Related
I have a THREE.js project that includes an imported gltf object:
loader.load("myObj.gltf",
function(gltf){
scene.add(gltf.scene);
},
undefined,
function(error){alert(error);});
Later, I clone the object:
let newObj = oldObj.clone();
In order to clear up memory, I have to dispose of the new cloned object, but not the original. How would I go about doing this? I tried dispose(), but that did not work.
If you clone 3D objects like meshes, lines or point clouds in three.js, their materials, textures and geometries are not cloned but shared. So it should be sufficient to just remove cloned objects from the scene.
You can read more about what type of entities have a dispose() method in the following guide: https://threejs.org/docs/index.html#manual/en/introduction/How-to-dispose-of-objects
I have created a box geometry as below,
const hand1geo = new THREE.BoxGeometry(2, 0.01, 0.2);
const material_sidehand = new THREE.MeshBasicMaterial({ color: 0x3cc1b7 });
const sidehand = new THREE.Mesh(hand1geo, material_sidehand);
What I want to do is to extract vertices from this box and I use this,
this.sidehand.attributes.position.array
And what I got is as following,
The picture of result. I really don't understand why it just spanned 72 elements(24 vectors) with same value. Why there are 24 vectors here and where have them been defined? Because I wanna use raycaster to do the collision detection later on.
I tried to use this.sidehand.vertices but it doesn't work.
I tried to use this.sidehand.vertices but it doesn't work.
I don't know what references you used but Mesh never had a property called vertices. You probably refer to the former Geometry class which indeed had this property. However, this class has been deprecated and BufferGeometry is used now instead.
I really don't understand why it just spanned 72 elements(24 vectors) with same value.
The values are not identical. BoxGeometry defines all vertices of the box in local space in a flat array so the data can be directly used by the WebGL API (which is good for performance).
There are 24 vectors because the geometry defines for each side of the box four vertices. Each side is composed of two triangles. This is done so it's possible to generate proper normals and texture coordinates.
I suggest you reconsider to use raw geometry data for collision detection. You are going to achieve much better performance by working with bounding volumes instead.
I've this model here in Three.js:
Unsmoothed part of the model
I used a plug-in from the Unity asset store, to export the model into a JSON file, then imported in my three.js application. The problem is that the plugin doesn't export the smoothing groups, so the quality of the model doesn't look so good.
Is there any way to smooth everything with three.js?
You can use the THREE.SubdivisionModifier and use it like this:
var modifier = new THREE.SubdivisionModifier(divisions);
// Apply the modifier to your geometry NOT MESH.
modifier.modify( geometry );
Actually it's not included in Three.js build, so you have to import it.
You can get it here
UPDATE 1
Basically you JSON file gets loaded as an Object3d, which is like a container. It's structured like this:
Object3D
children (arrays containing your meshes (the number can change based on the model in your scene))
Mesh (containing data about your geometry, which is what you need to modify).
So in order to modify the "geometry" you need to access it like so:
modifier.modify( mesh.children[0].children[0].geometry );
You'll need to apply a modifier to every model in your scene, so:
modifier.modify( mesh.children[0].children[0].geometry );
modifier.modify( mesh.children[0].children[1].geometry );
modifier.modify( mesh.children[0].children[2].geometry );
depending on the number of models you have.
It's like you have to open a container and inside you find a smaller container, then another one and so on, until you access geometry data. Hope it is clear enough :)
I have a 3D model of my home town. I would like to use real time data to change the height of the buildings. In my first try, I loaded the buildings as individual meshes and called scene.add(buildingMesh) during setup.
var threeObjects = []
var buildingMesh = new THREE.Mesh(geometry, material)
threeObjects.push(buildingMesh);
$.each(threeObjects,function(i, buildingMesh)
{
buildingMesh.rotation.x += -3.1415*0.5;
buildingMesh.castShadow = true;
buildingMesh.receiveShadow = true;
scene.add(buildingMesh);
});
Which is too slow as my dataset consists of roughly 10.000 building.
So I took the approach to add all the (geometries of the) meshes to a single geometry and wrap that in a mesh to be added to the scene
singleGeometry.merge(buildingMesh.geometry, buildingMesh.matrix); //in a loop
var faceColorMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, vertexColors: THREE.VertexColors } );
combinedMesh = new THREE.Mesh(singleGeometry, faceColorMaterial);
scene.add(combinedMesh);
Just to make a proof of concept, I'm trying to change the height of a building when I click it. Alas, this is not working.
By adding a new id field, I can get a reference to the faces and vertices and change the color of the building, but I can not for the life of me, get them to to change height.
In my first version, I would just use something like:
buildingMesh.scale.z=2;
But as I have no meshes anymore, I'm kinda lost.
Can anybody help?
disclaimer: I'm new to Three.js, so my question might be stupid...hope it's not :)
If you combine all of your buildings into a single geometry, you're obliterating everything that makes the buildings distinct from each other. Now you can't tell building A from building B because it's all one big geometry, and geometry at its basic level is literally just arrays of points and polygons with no way of telling any of it apart. So I think it's the wrong approach to merge it all together.
Instead, you should take advantage of three.js's efficient scene graph architecture. You had the right idea at first to just add all the buildings to a single root Object3D ("scene"). That way you get all the efficiencies of the scene graph but can still individually address the buildings.
To make it load more efficiently, instead of creating the scene graph in three.js every time you load the app, you should do it ahead of time in a 3D modeling program. Build the parent/child relationships there, and export it as a single model containing all of the buildings as child nodes. When you import it into three.js, it should retain its structure.
JCD: That was not quite the question I asked.
But anyhow, I found a solution to the problem.
What I did was to merge all the geometries, but in stead of using the standard clone function in geometry.merge() I used a shallow reference, which made it possible for me to use the reference in threeObjects to find the correct building and resize the part of the geometry using Mesh.scale, followed by a geometry.verticesNeedUpdate = true;
For further optimization, I split the model into 5 different geometries and only updated the geometry that contained the building
I am working on a project that is dynamically generating trees. It's currently only in an early prototype stages, and so the branches and leaves are just simple cubes. The tree is made up of a hierarchy of the cubes, nested with the rotations and scaling down of the branches. I need to be able to add more branches/leaves to the tree, but can convert it to a static tree just for rendering purposes.
I am running into a problem when the tree gets too large, rendering many cubes slowing the program down.
After doing a lot of research, I discovered the THREE.GeometryUtils.merge() function, that would merge all the branches in my tree into one object that can be rendered/transformed much faster than before. However the problem I am encountering is the merge doesn't take into account all of the parent transforms, merging only it's vertices.
The basic code I am trying to get working is as follows. I have played around with applying the matrix to the geometry and a few other things, but have not got anything working properly yet.
var newGeo = new THREE.Geometry();
var newTree = tree.clone(); //Clones the tree so the original does not get altered
newTree.traverse(function(child){
if(child.parent){
child.applyMatrix( child.parent.matrixWorld);
}
THREE.GeometryUtils.merge(newGeo, child);
});
I have created a simple jsFiddle program for it:
http://jsfiddle.net/JeYhF/2/
The left object is 4 meshes parented inside each other and the right object is the mesh combination. As you can see, each component of the combination has its own transforms applied (translation in y axis by 11 units and rotation in z axis by PI/4), but they are not affected by the parent transforms.
The function in question is the MergeTree() function. This program would only work in chrome for me.
Any advice for how to solve this problem would be very much appreciated.
Thanks
The problem was the matrix world was not calculated before the merging. So the transforms were all just the identity matrix.
newTree.traverse(function(child){
if(child.parent){
child.updateMatrixWorld();
child.applyMatrix(child.parent.matrixWorld);
}
THREE.GeometryUtils.merge(newGeo, child);
});