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

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)

Related

Three.js shadows acting weirdly

I am trying to create a little solar system but found a bug... or a feature. I'd like for all of my planets to be able to cast and receive shadows from all other planets. However, it seems as if it depends on instancing order if shadows are cast or not.
Code for the light and shadows:
const sunLight = new THREE.PointLight(0xffffff, 3, 100);
sunLight.position.set(0, 0, 0);
sunLight.castShadow = true
scene.add(sunLight);
//Set up shadow properties for the light
sunLight.shadow.mapSize.width = 512; // default
sunLight.shadow.mapSize.height = 512; // default
sunLight.shadow.camera.near = 0.5; // default
sunLight.shadow.camera.far = 500; // default
const sphereSize = 1;
const pointLightHelper = new THREE.PointLightHelper(sunLight, sphereSize);
scene.add(pointLightHelper);
const shadowHelper = new THREE.CameraHelper( sunLight.shadow.camera );
scene.add( shadowHelper );
Basic code for the code objects:
var earth = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 16),
new THREE.MeshStandardMaterial({
map: tLoader.load("/textures/nasa-world.jpg"),
bumpMap: tLoader.load("/textures/nasa-jpl-world-bump.png"),
bumpScale: 0.01,
}));
earth.castShadow = true
earth.receiveShadow = true
// position goes here
scene.add(earth);
var mars = new THREE.Mesh(
new THREE.SphereGeometry(0.53, 32, 16),
new THREE.MeshStandardMaterial({
map: tLoader.load("/textures/nasa-mars.jpg"),
bumpMap: tLoader.load("/textures/nasa-mars-bump.png"),
bumpScale: 0.01,
}))
mars.castShadow = true
mars.receiveShadow = true
//position goes here
scene.add(mars);
Case 1 (working shadow):
earth.position.x = 18
mars.position.x = 15
(https://ibb.co/gS26Sfz)
Case 2 (not working):
earth.position.x = 15
mars.position.x = 18
(https://ibb.co/PZrh2wS)
Case 3 (not sure why, but it works):
When I switch around the instancing (I first instance mars, then earth, Case 2 DOES work).
(https://ibb.co/pRz06b1)
It does seem to me that only objects that are instanced BEFORE the objects that drop shadows can actually receive shadows. I cannot imagine, though, that this is truly a limitation, I am probably doing something wrong.
Please help me, how can I make both objects cast and receive shadows from one another?
After playing around and reading some more documentation, it seems as if this problem is hard coded. The instancing order does seem to determine what can cast and receive shadows. In other words: the meshes that are to receive the shadow must be instanced before the objects casting the shadows. This is quite a limitation, in some ways.

Three js children length

I created a three js object and added some children to it. then i changed the length of children to 0. then the objects have gone out of screen. Will that make the objects fully removed from the screen and memory?
var balls = new THREE.Object3D(); // parent
for creating childrens
var geometry = new THREE.SphereGeometry(5, 32, 32);
var material = new THREE.MeshPhongMaterial({color: 0x0f0ff0, shininess: 50, transparent: true, opacity: 1});
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = scale('some random value');
sphere.position.y = scale('some random value');
balls.add(sphere);
above steps repeated for more spheres
then in the console i wrote
balls.children = [];
this removes all the spheres from the scene. Will that removes all the sphere objects from the memory also??
Yes, when you have an array and then set array.length = 0; all the elements of the array will be deleted. When you type array.length = 2, all elements other than the first two elements will be deleted.
Javascript has a function called slice() which does a similar thing.
The correct way for deleting a child is calling remove(child) from its parent, and then use dispose() on children's material and geometry.
In your code:
var balls = new THREE.Object3D(); // parent
var geometry = new THREE.SphereGeometry(5, 32, 32);
var material = new THREE.MeshPhongMaterial({color: 0x0f0ff0, shininess: 50, transparent: true, opacity: 1});
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = scale('some random value');
sphere.position.y = scale('some random value');
balls.add(sphere);
// Do some work
balls.remove(sphere);
geometry.dispose();
material.dispose();
Dispose the material/geometry only when it is not used by other Mesh anymore.
From THREE.Object3D at remove(object, ...):
"Removes object as child of this object. An arbitrary number of objects may be removed."
From THREE.Geometry at dispose():
"Don't forget to call this method when you remove a geometry because it can cause memory leaks."
From THREE.Material at dispose():
"This disposes the material. Textures of a material don't get disposed. These needs to be disposed by Texture."
If you use textures, you must dispose these too.
(THREE.js r85).

Three.js how to show/update Geometry Segments?

I have a simple TRHEE.BoxGeometry in a THREE.Scene. I want to show the outline of the Geometry Segments (not the Mesh).
I found an example here that does exactly that, but I can't reproduce.
Given example looks like image below.
How do I show segments outlines and how would I update the geometry at runtime?
You can create a second mesh duplicate using geometry.clone.
material = new THREE.MeshPhongMaterial();
var geometry = new THREE.BoxGeometry(200, 200, 200);
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var material2 = new THREE.MeshPhongMaterial({
color:0xFF0000,
wireframe:true
});
mesh2 = new THREE.Mesh(geometry.clone(), material2);
scene.add(mesh2);
Here is a fiddle: http://jsfiddle.net/nss1n5tv/
You can use wireframeLinewidth to specify the thickness of the line.

Three.js lines normal to a sphere

My goal is to create an interactive Earth that has lines normal to the surface so that you can click on them and it pulls up pictures that my health care team has taken from around the world. I have the world completely coded (or more accurately someone else did it and I made a few small changes).
Below is the code for the Earth which functions as expected. What I want to know is how to make lines normal to the surface and have them be clickable. It would be optimal if the lines faded and disappeared as they went to the back of the earth rotated or the user rotated the earth and the lines on the side the user couldn't see faded.
I thought about making an array of cities and having a location on the sphere be associated with it but I'm not really sure how to do that. I am very new to Three.js and HTML/JS in general.
it may be helpful to know that I am using three.mins.js, Detector.js, and TrackballControl.js
Code so far as follows:
(function () {
var webglEl = document.getElementById('webgl');
if (!Detector.webgl) {
Detector.addGetWebGLMessage(webglEl);
return;
}
var width = window.innerWidth,
height = window.innerHeight;
// Earth params
var radius = 0.5,
segments = 32,
rotation = 6;
var scene = new THREE.Scene();
var uniforms, mesh, meshes =[];
var camera = new THREE.PerspectiveCamera(45, width / height, 0.01, 1000);
camera.position.z = 1.5;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
scene.add(new THREE.AmbientLight(0x333333));
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5,3,5);
scene.add(light);
var sphere = createSphere(radius, segments);
sphere.rotation.y = rotation;
scene.add(sphere)
var clouds = createClouds(radius, segments);
clouds.rotation.y = rotation;
scene.add(clouds)
var stars = createStars(90, 64);
scene.add(stars);
var controls = new THREE.TrackballControls(camera);
webglEl.appendChild(renderer.domElement);
render();
function render() {
controls.update();
sphere.rotation.y += 0.0005;
clouds.rotation.y += 0.0007;
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function createSphere(radius, segments) {
return new THREE.Mesh(
new THREE.SphereGeometry(radius, segments, segments),
new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture('images/Color_Map.jpg'),
bumpMap: THREE.ImageUtils.loadTexture('images/elev_bump_4k.jpg'),
bumpScale: 0.005,
specularMap: THREE.ImageUtils.loadTexture('images/water_4k.png'),
specular: new THREE.Color('grey')
})
);
}
function createClouds(radius, segments) {
return new THREE.Mesh(
new THREE.SphereGeometry(radius + 0.003, segments, segments),
new THREE.MeshPhongMaterial({
map: THREE.ImageUtils.loadTexture('images/fair_clouds_4k.png'),
transparent: true
})
);
}
function createStars(radius, segments) {
return new THREE.Mesh(
new THREE.SphereGeometry(radius, segments, segments),
new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture('images/galaxy_starfield.png'),
side: THREE.BackSide
})
);
}
}());
The hope is that it would look like this link but with Earth and not a building (http://3d.cl3ver.com/uWfsD?tryitlocation=3) [also click explore when you go there].
I built a quick demo that most faithfully represents what I think your needs are. It shows some images that seem to be attached to an Earth sphere through lines. It uses sprites to create those images (and the lines themselves, actually). I think it resembles quite well that demo of a building that you linked to. Here is the technique:
Images are added using GIMP to this template and saved as PNGs.
Those images are loaded as textures in the Js apps.
The sprite is created, using the loaded texture.
The sprite is added to a Object3D and its position set to (0,0,radiusOfTheEarthSphere)
The Object3D is added to the sphere, and rotated until the center of the sprite lies in the position in Earth that you want it to rest in.
Each frame, a dot product between a vector from the center of Earth to the camera and a vector from the center of the Earth to each sprite is used to calculate the sprite's opacity.
That equation in 6 is:
opacity = ((|cameraPosition - centerOfEarth| x |spriteCenter - centerOfEarth|) + 1) * 0.5
where "x" is dot product and "||" denotes normalization.
Also note that sprite center is different from its position due to the Object3D used as parent, I calculate its center using the .localToWorld(vec) method.
Please see the demo here: https://33983769c6a202d6064de7bcf6c5ac7f51fd6d9e.googledrive.com/host/0B9scOMN0JFaXSE93YTZRTE5XeDQ/test.html
It is hosted in my Google Drive and it may take some time to load. Three.js will give some errors in the console untill all textures are loaded because I coded it quickly just to show you my implementation ideas.

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