Completely replace the transformation matrix of a three.js object/mesh - javascript

Create object
A three.js mesh object is created:
var geometry = new THREE.BufferGeometry();
var standardMaterial = new THREE.MeshStandardMaterial( {/* inputs */ } );
var mesh = new THREE.Mesh( geometry, standardMaterial );
// Then mesh is added as an object to 3D scene.
Set transformation matrix
I intend to set the transformation matrix of the object:
object = ... // Is already created and passed around.
const pkm = ... // Containing 16 numbers, equivalent of a 4x4 matrix.
var matrix = new THREE.Matrix4();
matrix.set(
pkm.X00, pkm.X01, pkm.X02, pkm.X03,
pkm.X10, pkm.X11, pkm.X12, pkm.X13,
pkm.X20, pkm.X21, pkm.X22, pkm.X23,
pkm.X30, pkm.X31, pkm.X32, pkm.X33,
);
object.applyMatrix4( matrix );
object.updateMatrixWorld( true );
Problem
The problem is that the above approach of setting transformation matrix just multiplies the new matrix into the previous matrix of the object. But we want the previous matrix to be completely replaced by the new matrix.
What is the best practice - most robust way - to replace the previous matrix of a three.js object/mesh with a completely new one?

Related

ThreeJS: Add Two Materials on an Mesh Object

In ThreeJS, it is possible to add more than one material to an Object3D/Mesh according to its documentation. We can use a single Material or use an array of Material:
Mesh typescript file class declaration and contructor (from ThreeJS src code):
export class Mesh<
TGeometry extends BufferGeometry = BufferGeometry,
TMaterial extends Material | Material[] = Material | Material[] // ### Here: Material[] ###
> extends Object3D {
constructor(geometry?: TGeometry, material?: TMaterial);
Here is my problem which I can't seem to be able to solve...
When I use a single Material, my Mesh is displayed. However, when I use two materials as an array, my Mesh won't display. I also don't have any error print out in the console when rendering the scene.
My ultimate goal here is to be able to have to separate materials for the inner and the outer part of my object.
Here is my code:
export function init(radius: number = 18.0, innerColor: number = 0xFFFFFF, outerColor: number = 0x444444) {
var obj = new Object3D();
loader.load(
objPath,
function(object){
obj = object;
const mesh = obj.children[0] as Mesh;
// WORKING:
mesh.material = new MeshPhongMaterial({color: outerColor});
// NOT WORKING: Using two materials
// mesh.material = new Array<Material>(new MeshPhongMaterial({color: outerColor}), new MeshPhongMaterial({color:innerColor}));
mesh.scale.setLength(radius)
scene.add(mesh);
},
function (error){
console.log(error)
}
);
}
Why can't I manage to see my object when using two materials ?
I known there was the MeshFaceMaterial in previous version and the material array acceptance of the contructor is supposed to be a replacement for that in some sens.
ThreeJS version: r128
Any help would be appreciated !
The easiest way is to clone your mesh and assign two separate materials, one for the inside, another for the outside:
const meshOuter = obj.children[0] as THREE.Mesh;
const meshInner = meshOuter.clone();
// Outer mesh shows front side
meshOuter.material = new THREE.MeshPhongMaterial({
color: outerColor,
side: THREE.FrontSide
});
// Inner mesh shows back side
meshInner.material = new THREE.MeshPhongMaterial({
color: innerColor,
side: THREE.BackSide
});
// Scale inner mesh down just a bit to avoid z-fighting
meshInner.scale.multiplyScalar(0.99);

Convert Euler angle to Vector using .Euler's "setFromVector3"

So the inputs are three euler angles x,y,z in radians
I would like to convert this to a Vector location X,Y,Z with center as origin.
So if its possible to use https://threejs.org/docs/#api/en/math/Euler.toVector3 to get the Vector, i would like to know how. And also the alternate mathematical(sin/cos) solution is also appreciated.
so in this snippet axesHelper represents the angle and the cube should be at the location based on the euler.Use Dat gui to live edit the rotations.
//add Axis to represent Euler
const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );
//add cube to represent Vector
const geometry = new THREE.BoxGeometry( 0.1, 0.1, 0.1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
render()
const gui = new GUI();
const angles={
degX:0,
degY:0,
degZ:0,
}
gui.add( angles, 'degX',0,360,1 ).onChange(function(){
axesHelper.rotation.x=THREE.MathUtils.degToRad(angles.degX)
render()
updateEULtoAngle()
});
gui.add( angles, 'degY',0,360,1 ).onChange(function(){
axesHelper.rotation.y=THREE.MathUtils.degToRad(angles.degY)
render()
updateEULtoAngle()
});
gui.add( angles, 'degZ',0,360,1 ).onChange(function(){
axesHelper.rotation.z=THREE.MathUtils.degToRad(angles.degZ)
render()
updateEULtoAngle()
});
console.log(THREE.MathUtils.radToDeg( axesHelper.rotation.x))
console.log(THREE.MathUtils.radToDeg( axesHelper.rotation.y))
console.log(THREE.MathUtils.radToDeg( axesHelper.rotation.z))
function updateEULtoAngle(){
let eul= new THREE.Euler(
THREE.MathUtils.degToRad(angles.degX),
THREE.MathUtils.degToRad(angles.degY),
THREE.MathUtils.degToRad(angles.degZ)
)
let vec= new THREE.Vector3()
eul.toVector3(vec)
console.log(eul,vec)
cube.position.copy(vec)
}
fake visual representation
cube following the axes Y axis
related: but has problem with axis matching How to convert Euler angles to directional vector?
Euler.toVector3() does not do what you are looking for. It just copies the x, y and z angles into the respective vector components.
I think you should have a look at THREE.Spherical which is an implementation for using spherical coordinates. You can express a point in 3D space with two angles (phi and theta) and a radius. It's then possible to use these data to setup an instance of Vector3 via Vector3.setFromSpherical() or Vector3.setFromSphericalCoords().
im very not sure whats going on but
let vec = new THREE.Vector3(0, 0, 1).applyEuler(eul)
worked for me
also check this:
https://github.com/mrdoob/three.js/issues/1606

Three.js: Applying matrix to group of objects and then updating their locations

When we add objects from a scene to a group(now Object3D()), how do we apply the correct matrix for the group objects, so their locations would get updated in the scene.
So, as far as I know -by default ; object automatically update their matrices if they have been added to the scene with
var object = new THREE.Object3D();
scene.add( object );
if they are the child of another object that has been added to the scene:
var object1 = new THREE.Object3D();
var object2 = new THREE.Object3D();
object1.add( object2 );
scene.add( object1 );
//object1 and object2 will automatically update their matrices
There is more here

How to merge two geometries or meshes using three.js r71?

Here I bumped to the problem since I need to merge two geometries (or meshes) to one. Using the earlier versions of three.js there was a nice function:
THREE.GeometryUtils.merge(pendulum, ball);
However, it is not on the new version anymore.
I tried to merge pendulum and ball with the following code:
ball is a mesh.
var ballGeo = new THREE.SphereGeometry(24,35,35);
var ballMat = new THREE.MeshPhongMaterial({color: 0xF7FE2E});
var ball = new THREE.Mesh(ballGeo, ballMat);
ball.position.set(0,0,0);
var pendulum = new THREE.CylinderGeometry(1, 1, 20, 16);
ball.updateMatrix();
pendulum.merge(ball.geometry, ball.matrix);
scene.add(pendulum);
After all, I got the following error:
THREE.Object3D.add: object not an instance of THREE.Object3D. THREE.CylinderGeometry {uuid: "688B0EB1-70F7-4C51-86DB-5B1B90A8A24C", name: "", type: "CylinderGeometry", vertices: Array[1332], colors: Array[0]…}THREE.error # three_r71.js:35THREE.Object3D.add # three_r71.js:7770(anonymous function) # pendulum.js:20
To explain Darius' answer more clearly (as I struggled with it, while trying to update a version of Mr Doob's procedural city to work with the Face3 boxes):
Essentially you are merging all of your Meshes into a single Geometry. So, if you, for instance, want to merge a box and sphere:
var box = new THREE.BoxGeometry(1, 1, 1);
var sphere = new THREE.SphereGeometry(.65, 32, 32);
...into a single geometry:
var singleGeometry = new THREE.Geometry();
...you would create a Mesh for each geometry:
var boxMesh = new THREE.Mesh(box);
var sphereMesh = new THREE.Mesh(sphere);
...then call the merge method of the single geometry for each, passing the geometry and matrix of each into the method:
boxMesh.updateMatrix(); // as needed
singleGeometry.merge(boxMesh.geometry, boxMesh.matrix);
sphereMesh.updateMatrix(); // as needed
singleGeometry.merge(sphereMesh.geometry, sphereMesh.matrix);
Once merged, create a mesh from the single geometry and add to the scene:
var material = new THREE.MeshPhongMaterial({color: 0xFF0000});
var mesh = new THREE.Mesh(singleGeometry, material);
scene.add(mesh);
A working example:
<!DOCTYPE html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.js"></script>
<!-- OrbitControls.js is not versioned and may stop working with r77 -->
<script src='http://threejs.org/examples/js/controls/OrbitControls.js'></script>
<body style='margin: 0px; background-color: #bbbbbb; overflow: hidden;'>
<script>
// init renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// init scene and camera
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 3000);
camera.position.z = 5;
var controls = new THREE.OrbitControls(camera)
// our code
var box = new THREE.BoxGeometry(1, 1, 1);
var sphere = new THREE.SphereGeometry(.65, 32, 32);
var singleGeometry = new THREE.Geometry();
var boxMesh = new THREE.Mesh(box);
var sphereMesh = new THREE.Mesh(sphere);
boxMesh.updateMatrix(); // as needed
singleGeometry.merge(boxMesh.geometry, boxMesh.matrix);
sphereMesh.updateMatrix(); // as needed
singleGeometry.merge(sphereMesh.geometry, sphereMesh.matrix);
var material = new THREE.MeshPhongMaterial({color: 0xFF0000});
var mesh = new THREE.Mesh(singleGeometry, material);
scene.add(mesh);
// a light
var light = new THREE.HemisphereLight(0xfffff0, 0x101020, 1.25);
light.position.set(0.75, 1, 0.25);
scene.add(light);
// render
requestAnimationFrame(function animate(){
requestAnimationFrame(animate);
renderer.render(scene, camera);
})
</script>
</body>
At least, that's how I am interpreting things; apologies to anyone if I have something wrong, as I am no where close to being a three.js expert (currently learning). I just had the "bad luck" to try my hand at customizing Mr. Doob's procedural city code, when the latest version breaks things (the merge stuff being one of them, the fact that three.js no longer uses quads for cube -ahem- box geometry the other - which has led to all kinds of fun getting the shading and such to work properly again).
Finally, I found a possible solution. I am posting since it could be useful for somebody else while I wasted a lot of hours. The tricky thing is about manipulating the concept of meshes and geometries:
var ballGeo = new THREE.SphereGeometry(10,35,35);
var material = new THREE.MeshPhongMaterial({color: 0xF7FE2E});
var ball = new THREE.Mesh(ballGeo, material);
var pendulumGeo = new THREE.CylinderGeometry(1, 1, 50, 16);
ball.updateMatrix();
pendulumGeo.merge(ball.geometry, ball.matrix);
var pendulum = new THREE.Mesh(pendulumGeo, material);
scene.add(pendulum);
The error message is right. CylinderGeometry is not an Object3D. Mesh is. A Mesh is constructed from a Geometry and a Material. A Mesh can be added to the scene, while a Geometry cannot.
In the newest versions of three.js, Geometry has two merge methods: merge and mergeMesh.
merge takes a mandatory argument geometry, and two optional arguments matrix and materialIndexOffset.
geom.mergeMesh(mesh) is basically a shorthand for geom.merge(mesh.geometry, mesh.matrix), as used in other answers. ('geom' and 'mesh' being arbitrary names for a Geometry and a Mesh, respectively.) The Material of the Mesh is ignored.
This is my ultimate compact version in four (or five) lines (as long as material is defined somewhere else) making use of mergeMesh:
var geom = new THREE.Geometry();
geom.mergeMesh(new THREE.Mesh(new THREE.BoxGeometry(2,20,2)));
geom.mergeMesh(new THREE.Mesh(new THREE.BoxGeometry(5,5,5)));
geom.mergeVertices(); // optional
scene.add(new THREE.Mesh(geom, material));
Edit: added optional extra line to remove duplicate vertices, which should help performance.
Edit 2: I'm using the newest version, 94.
The answers and code that I've seen posted here do not work because the second argument of the merge method is an integer, not a matrix. As far as I can tell, the merge method is not really functioning in a useful way. Therefore, I used the following approach to make a simple rocket with a nose cone.
import * as BufferGeometryUtils from '../three.js/examples/jsm/utils/BufferGeometryUtils.js'
lengthSegments = 2
radius = 5
radialSegments = 32
const bodyLength = dParamWithUnits['launchVehicleBodyLength'].value
const noseConeLength = dParamWithUnits['launchVehicleNoseConeLength'].value
// Create the vehicle's body
const launchVehicleBodyGeometry = new THREE.CylinderGeometry(radius, radius, bodyLength, radialSegments, lengthSegments, false)
launchVehicleBodyGeometry.name = "body"
// Create the nose cone
const launchVehicleNoseConeGeometry = new THREE.CylinderGeometry(0, radius, noseConeLength, radialSegments, lengthSegments, false)
launchVehicleNoseConeGeometry.name = "noseCone"
launchVehicleNoseConeGeometry.translate(0, (bodyLength+noseConeLength)/2, 0)
// Merge the nosecone into the body
const launchVehicleGeometry = BufferGeometryUtils.mergeBufferGeometries([launchVehicleBodyGeometry, launchVehicleNoseConeGeometry])
// Rotate the vehicle to horizontal
launchVehicleGeometry.rotateX(-Math.PI/2)
const launchVehicleMaterial = new THREE.MeshPhongMaterial( {color: 0x7f3f00})
const launchVehicleMesh = new THREE.Mesh(launchVehicleGeometry, launchVehicleMaterial)

How to get correct values for normals in threejs?

I don’t understand how normals are computed in threejs.
Here is my problem :
I create a simple plane
var plane = new THREE.PlaneGeometry(10, 100, 10, 10);
var material = new THREE.MeshBasicMaterial();
material.setValues({side: THREE.DoubleSide, color: 0xaabbcc});
var mesh = new THREE.Mesh(plane, material);
mesh.rotateY(Math.PI / 2);
scene.add(mesh);
When I read the normal of this plane, I get (0, 0, 1).
But the plane is parallel to the z axis so the value is wrong.
I tried adding
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
but I still get the same result.
Did I miss anything ?
How can I get correct values for normals from threejs ?
Thanks.
Geometry normals are in object space. To transform them to world space, first make sure the object matrix is updated.
object.updateMatrixWorld();
(The renderer does this for you in each render loop, so you may be able to skip this step.)
Then, compute the normal matrix:
var normalMatrix = new THREE.Matrix3().getNormalMatrix( object.matrixWorld );
Now transform the normal to world space like so:
var newNormal = normal.clone().applyMatrix3( normalMatrix ).normalize();
three.js r.66

Categories