Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Three.js - bind two shapes together as one?

Can I bind two different shapes together as one shape?

For example, binding sphere and cylinder together as one?

like image 913
BorisD Avatar asked Nov 30 '11 08:11

BorisD


2 Answers

Kind of, yes. There are multiple options:

  1. via hierarchy you can simply add one mesh to another using the add() function
  2. via the GeometryUtil's merge() function to merge vertices and meshes of two Geometry objects into one
  3. using a basic 3D editor that supports Boolean operations between meshes and exporting.

Method 1 is pretty straightforward:

var sphere = new THREE.Mesh(new THREE.SphereGeometry(100, 16, 12), new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading }));

var cylinder = new THREE.Mesh(new THREE.CylinderGeometry(100, 100, 200, 16, 4, false), new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading } ));
cylinder.position.y = -100;
scene.add(sphere);
scene.add(cylinder);

Notice that 16 is repeated, so the subdivisions level in one mesh matches the other (for a decent look).

Method 2.1 - via GeometryUtils

// Make a sphere

var sg = new THREE.SphereGeometry(100, 16, 12);
// Make a cylinder - ideally the segmentation would be similar to predictable results
var cg = new THREE.CylinderGeometry(100, 100, 200, 16, 4, false);
// Move vertices down for cylinder, so it maches half the sphere - offset pivot
for(var i = 0 ; i < cg.vertices.length; i++)
    cg.vertices[i].position.y -= 100;

// Merge meshes
THREE.GeometryUtils.merge(sg, cg);
var mesh = new THREE.Mesh(sg, new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading }));
scene.add(mesh);

Method 2.2 merging a Lathe half-sphere and a cylinder:

var pts = []; // Points array

var detail = .1; // Half-circle detail - how many angle increments will be used to generate points
var radius = 100; // Radius for half_sphere
var total = Math.PI * .51;
for(var angle = 0.0; angle < total ; angle+= detail) // Loop from 0.0 radians to PI (0 - 180 degrees)
    pts.push(new THREE.Vector3(0,Math.cos(angle) * radius,Math.sin(angle) * radius)); // Angle/radius to x,z

var lathe = new THREE.LatheGeometry(pts, 16); // Create the lathe with 12 radial repetitions of the profile

// Rotate vertices in lathe geometry by 90 degrees
var rx90 = new THREE.Matrix4();
rx90.setRotationFromEuler(new THREE.Vector3(-Math.PI * .5, 0, 0));
lathe.applyMatrix(rx90);

// Make cylinder - ideally the segmentation would be similar for predictable results
var cg = new THREE.CylinderGeometry(100, 100, 200, 16, 4, false);

// Move vertices down for cylinder, so it maches half the sphere
for(var i = 0 ; i < cg.vertices.length; i++)
    cg.vertices[i].position.y -= 100;

// Merge meshes
THREE.GeometryUtils.merge(lathe, cg);
var mesh = new THREE.Mesh(lathe, new THREE.MeshLambertMaterial({ color: 0x2D303D, wireframe: true, shading: THREE.FlatShading}));
mesh.position.y = 150;
scene.add(mesh);

The one problem I can't address at the moment comes from the faces that are inside the mesh. Ideally, those would have normals flipped, so they wouldn't render, but I haven't found a quick solution for that.

The third is fairly straightforward. Most 3D packages allow Boolean operation on meshes (e.g., merging two meshes together with the ADD operation (meshA + meshB)). Try creating a cylinder and a sphere in Blender (free and opensource), which already has a Three.js exporter. Alternatively you can export an .obj file of the merged meshes from your 3D editor or choice and use the convert_obj_three script.

I've found yet another method, which might be easier/more intuitive. Remember the Boolean operations I've mentioned above?

It turns out there is an awesome JavaScript library just for that: Constructive Solid Geometry:

CSG library preview from the owner's GitHub page

Chandler Prall wrote some handy functions to connect CSG with three.js. So with the CSG library and the Three.js wrapper for it, you can simply do this:

var cylinder = THREE.CSG.toCSG(new THREE.CylinderGeometry(100, 100, 200, 16, 4, false), new THREE.Vector3(0, -100, 0));
var sphere   = THREE.CSG.toCSG(new THREE.SphereGeometry(100, 16, 12));
var geometry = cylinder.union(sphere);
var mesh     = new THREE.Mesh(THREE.CSG.fromCSG(geometry), new THREE.MeshNormalMaterial());

Which gives you a nice result (no problems with extra faces/flipping normals, etc.):

Cylinder-sphere union with CSG and Three.js

like image 98
George Profenza Avatar answered Nov 13 '22 07:11

George Profenza


I updated wrapper for THREE.js r62, you can find it here: https://github.com/kraag22/csg-wrapper

like image 23
kraag22 Avatar answered Sep 22 '22 17:09

kraag22