I have a Three.js Object3D that I would like to update the colour of.
I originally construct the Mesh using a MeshStandardMaterial and add it into the scene. Later, I look up this object by ID and retrieve an Object3D from the scene. How can I update the colour of the Mesh at this point - is it possible?
If I have to delete the 3D object and add an entirely new one - is there a way to retrieve the geometry that was originally used to construct it from the Object3D itself? I would rather not store a mapping of the original geometry to an object's ID, as this would make the code messy. One option I can think of is to store the geometry on Object3D.UserData, but this is again suboptimal as currently the Meshes are constructed elsewhere - and then added to the scene (user data is available only once it has been added to the scene).
I find a way to add texture into a Object3D.
You can map children property to update material of meshes.
function onLoad(object) {
// `children` is an array of `Mesh` than contain 1 or more Meshes
object.children.forEach((mesh) => {
if (!mesh) return;
const material = mesh.material;
material.map = ghostColorTexture;
material.normalMap = ghostNormalTexture;
});
}
const objLoader = new OBJLoader();
objLoader.load(
'/models/ghost/model/ghost.obj',
onLoad,
undefined,
function (error) {
console.error('error', error);
}
);
An Object3D can be a Mesh that's composed of a Geometry and a Material. If you want to update the color, simply select that material, and assign a new color to it:
object.material.color.setHex(0xff9900); // Sets to orange
In the docs, you can see that .color is a property of MeshStandardMaterial and .material is a property of Mesh
Related
I'm rendering an object with textures using MTL and OBJ files with Three.js. My code here works but my model is displayed as flat shaded. How do I enable smooth shading?
var scene = new THREE.Scene();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath('assets/');
mtlLoader.setBaseUrl('assets/');
mtlLoader.load('asset.mtl', function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('assets/');
objLoader.load('asset.obj', function(object) {
//
// This solved my problem
//
object.traverse(function(child) {
if(child instanceof THREE.Mesh)
{
child.material.shading = THREE.SmoothShading;
}
});
//
//
scene.add(object);
});
});
EDIT:
I updated my code with a solution that fixed my problem based on the accepted answer.
It could be one of two things that I can think of right now.
It could be that the material is set to FlatShading. In this case just somehow retrieve the object and use object.material.shading = THREE.SmoothShading; to fix.
If that doesn't change it, it's possible that the object contains per-vertex-normals (meaning that every vertex of every triangle has a normal attached to it) and that all normals for each triangle point in the same direction. This is something that should better be solved in the 3d-editing process, but you can also re-compute the normals in three.js:
object.geometry.computeVertexNormals(true);
This should [1] recompute the normals for smooth surfaces. However, it will only work for regular Geometries and Indexed BufferGeometries (or, to put it the other way around: it won't work if the geometry doesn't have information about vertices being reused for adjacent faces)
[1]: I didn't test it myself and just go after what I just read in the code
You may need to smooth geometry as follows:
geometry = BufferGeometryUtils.mergeVertices(geometry, 0.1);
geometry.computeVertexNormals(true);
One can easily create a THREE.BoxGeometry where you have to pass arguments when creating as three separated arguments for width, height, and depth.
I would like to create any and all THREE[types]() with no parameters and set the values after that.
Is there a way to set the dimensions/size of the box geometry after creation (possibly buried in a Mesh already too)? other then scaling etc.
I couldn't find this in the documentation if so, otherwise maybe a major feature request if not a bug there. Any thoughts on how to classify this? maybe just a documentation change.
If you want to scale a mesh, you have two choices: scale the mesh
mesh.scale.set( x, y, z );
or scale the mesh's geometry
mesh.geometry.scale( x, y, z );
The first method modifies the mesh's matrix transform.
The second method modifies the vertices of the geometry.
Look at the source code so you understand what each scale method is doing.
three.js r.73
When you instantiate a BoxGeometry object, or any other geometry for that matter, the vertices and such buffers are created on the spot using the parameters provided. As such, it is not possible to simply change a property of the geometry and have the vertices update; the entire object must be re-instantiated.
You will need to create your geometries as you have the parameters for them available. You can however create meshes without geometries, add them to a scene, and update the mesh's geometry property once you have enough information to instantiate the object. If not that, you could also set a default value at first and then scale to reach your target.
Technically, scaling only creates the illusion of an updated geometry and the question did say (other then scaling). So, I would say a better approach would be to reassign the geometry property of your mesh to a new geometry.
mesh.geometry = new THREE.BoxGeometry(newSize, newSize, newSize)
With this approach you can update any aspect of the geometry including width segments for example. This is especially useful when working with non box geometries like cylinders or spheres.
Here is a full working example using this approach:
let size = 10
let newSize = 20
// Create a blank geometry and make a mesh from it.
let geometry = new THREE.BoxGeometry()
let material = new THREE.MeshNormalMaterial()
let mesh = new THREE.Mesh(geometry, material)
// Adding this mesh to the scene won't display anything because ...
// the geometry has no parameters yet.
scene.add(mesh)
// Unless you intend to reuse your old geometry dispose of it...
// this will significantly reduce memory footprint.
mesh.geometry.dispose()
// Update the mesh geometry to a new geometry with whatever parameters you desire.
// You will now see these changes reflected in the scene.
mesh.geometry = new THREE.BoxGeometry(size, size, size)
// You can update the geometry as many times as you like.
// This can be done before or after adding the mesh to the scene.
mesh.geometry = new THREE.BoxGeometry(newSize, newSize, newSize)
The problem:
I am looking to update the vertice positions on a mesh after scaling. I am doing this because I need to calculate the volume of the mesh. I am creating a cloned mesh to do this because I need to keep scale an active parameter in the origin mesh.
I was using the answer at How to update vertices geometry after rotate or move object
Which worked fine with three.js release .70, but has broken on release .72
My code:
var volumeClone = new THREE.Mesh (this.mesh.geometry.clone(), new THREE.MeshBasicMaterial( { color: 0xff0000 } ));
volumeClone.scale.z = heightScale;
volumeClone.updateMatrix();
volumeClone.geometry.applyMatrix( volumeClone.matrix );
volumeClone.matrix.identity();
volumeClone.geometry.verticesNeedUpdate = true;
volumeClone.scale.set( 1, 1, 1 );
console(calculateVolume(volumeClone));
Result:
In Chrome is:
Uncaught TypeError: Cannot read property 'setFromPoints' of undefined
THREE.Geometry.computeBoundingBox
#lib.min.js:3THREE.Geometry.applyMatrix
What I have tried:
I have attempted to investigate and isolate the issue within Geometry.js and Box3.js as well as understand the programmatic flow to understand why "this.boundBox" is undefined, but I haven't found the issue.
Question:
Is the syntax correct? Was there an update to three.js in this area?
The answer turned out to be that extrudeGeometry does not contain the same properties as regular geometry and so doing work on it doesn't work since particulars are missing. The answer is to convert bufferGeometry to regular geometry before trying to work with it as regular geometry. I understand the performance benefits of using buffers, but in this case I can create a geometry for measurements and trash it when I get a result.
So rather than "clone" geometry as shown in my question, I switch to creating the particular type Geometry from the buffer instance:
var volumeClone = new THREE.Mesh (new THREE.Geometry().fromBufferGeometry( this.mesh.geometry._bufferGeometry ), new THREE.MeshBasicMaterial( { color: 0xff0000 } ));
volumeClone.scale.z = heightScale;
volumeClone.updateMatrix();
volumeClone.geometry.applyMatrix( volumeClone.matrix );
volumeClone.matrix.identity();
volumeClone.geometry.verticesNeedUpdate = true;
volumeClone.scale.set( 1, 1, 1 );
console(calculateVolume(volumeClone));
This has the added benefit of working with any geometry type I have thrown at it thus far: extrusions, imported mesh, primitives...
I would like to draw a coloured box, including the edges (in a different colour)
This is the code I am using to create the box and edges. I have added them both to a object3D since I will have a number of differnt objects in the scene and would like to enforce encapsulation.
var mesh = new THREE.Mesh( geo, material );
var edge = new THREE.EdgesHelper( mesh, 0xffffff );
var container = new THREE.Object3D();
container.add(mesh);
container.add(edge);
scene.add(container);
The above posted code works and produces this:
But when I change the position of the parent object like so:
container.position.set(0,30,0);
Only the edges are moved. Why?
Adding .updateMatrix() to the container, mesh, or edges objects doesnt seem to produce different results.
How can the parent object be moved, included all of its children?
You need to add the helpers as children of the scene directly.
This is due to the following lines that appear in many of the helpers:
this.matrix = object.matrixWorld;
this.matrixAutoUpdate = false;
three.js r.70
is it possible to load a scene (e.g. two different cubes) exported from blender to json and identify them?
I need to distinguish between them e.g. to make one rotating and the other moving.
Thank you in advance!
Denv
edit+++
Thank you for your answer!
So if I load two cubes in one JSON file:
loader.load("untitled1.js", function(geometry, materials) {
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(materials));
mesh.scale.set( 10, 10, 10 );
mesh.position.y = 0;
mesh.position.x = 0;
scene.add( mesh );
});
How can I move first cube?
mesh.getObjectById(0).position.x = 15;
Doesn't seems to work.
Thank you!
Yes, it is possible to load an entire scene from a json file exported from Blender!
I achieved that with the following process: (Using three.js r80)
First you have to name your different objects in Blender like in the following image Outliner.
Then you can export the file using Three.js json exporter add-on for Blender, but taking care of marking the Scene checkbox like in the following:
Using this option your json file now contains all the meshes on the Blender's Outliner, as you can verify using any text editor. It doesn't matter if the meshes were selected or not.
It is important to know that the root (or parent) object isn't a Geometry anymore. It is labeled with the Object type by now. To access the children objects (of Mesh type) you can use the getObjectByName method on the root object like in the following code:
jsonloader.load( "obj/Books.json", function ( loadedObj ) {
var surface = loadedObj.getObjectByName("Surface");
var outline = loadedObj.getObjectByName("Outline");
var mask = loadedObj.getObjectByName("Mask");
// Watch the objects properties on console:
console.log(loadedObj);
console.log(surface);
console.log(outline);
console.log(mask);
} );
If we check the browser's console, we can see the proper objects assigned. And from now, you can manipulate the children objects independently (move, rotate, change materials, etc.)
Each object loaded has an associated .id. So you can use the Object3D.getObjectById() to find it and apply transforms on it.