14

I have this to create a line between 2 points:

var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0, 0, 0));
geometry.vertices.push(new THREE.Vector3(20, 100, 55));
var line = new THREE.Line(geometry, material, parameters = { linewidth: 400 });
scene.add(line);

(The line width, does not have any effect.)

My question is how do I transform this to a cylinder? I want to create a cylinder between two points.

Rails beginner
  • 14,321
  • 35
  • 137
  • 257
  • this seems to be a very similar issue to that one: [display a cylinder between 2 vectors][1] [1]: http://stackoverflow.com/questions/15139649/three-js-two-points-one-cylinder-align-issue/15160850#15160850 – jdregister Mar 11 '13 at 15:59
  • This is a duplicate of http://stackoverflow.com/questions/9038465/three-js-object3d-rotation-to-align-to-a-vector/31987883#31987883 – peterjwest Jan 19 '17 at 18:14

10 Answers10

11

I've had the exact same problem -- in WebGL the line width is always 1. So here's a function I wrote that will take two Vector3 objects and produce a cylinder mesh:

var cylinderMesh = function( pointX, pointY )
{
    // edge from X to Y
    var direction = new THREE.Vector3().subVectors( pointY, pointX );
    var arrow = new THREE.ArrowHelper( direction, pointX );

    // cylinder: radiusAtTop, radiusAtBottom, 
    //     height, radiusSegments, heightSegments
    var edgeGeometry = new THREE.CylinderGeometry( 2, 2, direction.length(), 6, 4 );

    var edge = new THREE.Mesh( edgeGeometry, 
        new THREE.MeshBasicMaterial( { color: 0x0000ff } ) );
    edge.rotation = arrow.rotation.clone();
    edge.position = new THREE.Vector3().addVectors( pointX, direction.multiplyScalar(0.5) );
    return edge;
}
Stemkoski
  • 8,936
  • 3
  • 47
  • 61
  • 1
    It should be `.subVectors( pointY, pointX )` and `.addVectors( pointX, direction.multiplyScalar(0.5) )` – mrdoob Mar 09 '13 at 22:47
  • Does not seem to have any effect. Think I have made a mistake. The vector line appears, but no cylinder and there are no errors.. Here is the code I have used: http://pastie.org/private/znr7wa4d4delrjifxbegq – Rails beginner Mar 10 '13 at 19:44
  • i think the error will be somewhere here `edge.rotation = arrow.rotation.clone()` but i didn't bother to check and ended up rotating manually instead – user151496 Feb 03 '16 at 15:38
  • position and rotation of Object3D are now read only, so the last two lines should be: edge.setRotationFromEuler(arrow.rotation); edge.position.copy(direction.multiplyScalar(0.5).add(pointX)); – Eran Aug 26 '22 at 14:54
8

As of December 2020:

I've tried to implement exactly what has been said in the previous responses, but nothing worked.

My solution:

cylinderMesh = function (pointX, pointY) {
  // edge from X to Y
  var direction = new THREE.Vector3().subVectors(pointY, pointX);
  const material = new THREE.MeshBasicMaterial({ color: 0x5B5B5B });
  // Make the geometry (of "direction" length)
  var geometry = new THREE.CylinderGeometry(0.04, 0.04, direction.length(), 6, 4, true);
  // shift it so one end rests on the origin
  geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, direction.length() / 2, 0));
  // rotate it the right way for lookAt to work
  geometry.applyMatrix(new THREE.Matrix4().makeRotationX(THREE.Math.degToRad(90)));
  // Make a mesh with the geometry
  var mesh = new THREE.Mesh(geometry, material);
  // Position it where we want
  mesh.position.copy(pointX);
  // And make it point to where we want
  mesh.lookAt(pointY);

  return mesh;
}
Gangula
  • 5,193
  • 4
  • 30
  • 59
4

Actually non of the other answers worked for me but to give credit I used the code that adjusts the position of edge and works fine.

Using parts of this answer and looking at the source of threejs I ended up to the following

var cylinderMesh = function( pointX, pointY )
{
    /* edge from X to Y */
    var direction = new THREE.Vector3().subVectors( pointY, pointX );
    var orientation = new THREE.Matrix4();
    /* THREE.Object3D().up (=Y) default orientation for all objects */
    orientation.lookAt(pointX, pointY, new THREE.Object3D().up);
    /* rotation around axis X by -90 degrees 
     * matches the default orientation Y 
     * with the orientation of looking Z */
    orientation.multiply(new THREE.Matrix4(1,0,0,0,
                                            0,0,1,0, 
                                            0,-1,0,0,
                                            0,0,0,1));

    /* cylinder: radiusAtTop, radiusAtBottom, 
        height, radiusSegments, heightSegments */
    var edgeGeometry = new THREE.CylinderGeometry( 2, 2, direction.length(), 8, 1);
    var edge = new THREE.Mesh( edgeGeometry, 
            new THREE.MeshBasicMaterial( { color: 0x0000ff } ) );

    edge.applyMatrix(orientation)
    edge.position = new THREE.Vector3().addVectors( pointX, direction.multiplyScalar(0.5) );
    return edge;
}

A possible improvement is to add edgeGeometry and material as parameters so that someone can reuse the same objects and not create a new one in every call

Community
  • 1
  • 1
kon psych
  • 626
  • 1
  • 11
  • 26
  • Works as of Oct 2017 - other solutions on this page are out of date. – David Oct 20 '17 at 20:37
  • This didn't work for me on r84. I had to instead first create a Matrix4, then set parameters on it. Like this: var m = new THREE.Matrix4(); m.set(1,0,0,0,0,0,1,0,0,-1,0,0,0,0,0,1); orientation.multiply(m); – Stian Jensen Dec 03 '17 at 15:10
4

Stopped working for me in version 70, but with this update it does :)

function cylinderMesh(pointX, pointY, material) {
            var direction = new THREE.Vector3().subVectors(pointY, pointX);
            var orientation = new THREE.Matrix4();
            orientation.lookAt(pointX, pointY, new THREE.Object3D().up);
            orientation.multiply(new THREE.Matrix4().set(1, 0, 0, 0,
                0, 0, 1, 0,
                0, -1, 0, 0,
                0, 0, 0, 1));
            var edgeGeometry = new THREE.CylinderGeometry(2, 2, direction.length(), 8, 1);
            var edge = new THREE.Mesh(edgeGeometry, material);
            edge.applyMatrix(orientation);
            // position based on midpoints - there may be a better solution than this
            edge.position.x = (pointY.x + pointX.x) / 2;
            edge.position.y = (pointY.y + pointX.y) / 2;
            edge.position.z = (pointY.z + pointX.z) / 2;
            return edge;
        }
danivicario
  • 1,673
  • 1
  • 16
  • 23
2

ArrowHelper since pull request #3307 are based on quaternions.

This works in three.js r58:

    var cylinderMesh = function(point1, point2, material)
    {
        var direction = new THREE.Vector3().subVectors(point2, point1);
        var arrow = new THREE.ArrowHelper(direction.clone().normalize(), point1);

        var rotation = new THREE.Vector3().setEulerFromQuaternion(arrow.quaternion);

        var edgeGeometry = new THREE.CylinderGeometry( 2, 2, direction.length(), 10, 4 );

        var edge = new THREE.Mesh(edgeGeometry, material);
        edge.rotation = rotation.clone();
        edge.position = new THREE.Vector3().addVectors(point1, direction.multiplyScalar(0.5));

        return edge;
    }
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
medihack
  • 16,045
  • 21
  • 90
  • 134
2

I figured I would post this since the answers didn't get me 100% there. I noticed the cylinders were correctly oriented, but they were positioned in reference to the origin. The below code (based on the other answers to this question) worked for me:

function cylinderMesh(pointX, pointY, material) {
    var direction = new THREE.Vector3().subVectors(pointY, pointX);
    var orientation = new THREE.Matrix4();
    orientation.lookAt(pointX, pointY, new THREE.Object3D().up);
    orientation.multiply(new THREE.Matrix4(1, 0, 0, 0,
                                           0, 0, 1, 0,
                                           0, -1, 0, 0,
                                           0, 0, 0, 1));
    var edgeGeometry = new THREE.CylinderGeometry(2, 2, direction.length(), 8, 1);
    var edge = new THREE.Mesh(edgeGeometry, material);
    edge.applyMatrix(orientation);
    // position based on midpoints - there may be a better solution than this
    edge.position.x = (pointY.x + pointX.x) / 2;
    edge.position.y = (pointY.y + pointX.y) / 2;
    edge.position.z = (pointY.z + pointX.z) / 2;
    return edge;
}

Hope this helps someone!

Kenny Thompson
  • 1,494
  • 12
  • 28
2

A solution working with r94:

function cylindricalSegment(A, B, radius, material) {
  var vec = B.clone(); vec.sub(A);
  var h = vec.length();
  vec.normalize();
  var quaternion = new THREE.Quaternion();
  quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), vec);
  var geometry = new THREE.CylinderGeometry(radius, radius, h, 32);
  geometry.translate(0, h / 2, 0);
  var cylinder = new THREE.Mesh(geometry, material);
  cylinder.applyQuaternion(quaternion);
  cylinder.position.set(A.x, A.y, A.z);
  return cylinder;
}

EDIT (2022)

It seems to me that this solution does not work anymore. Anyway the following one is more efficient (assuming all your cylinders have the same radius):

    const radius = 0.07;
    let geomUnitCylinder = new THREE.CylinderGeometry(radius, radius, 1, 64, 1, true);
    geomUnitCylinder.translate(0, 0.5, 0);

    const J = new THREE.Vector3(0, 1, 0);

    function geomTube(P, Q) {
        let vec = Q.clone().sub(P);
        const h = vec.length();
        vec.normalize();
        const quaternion = new THREE.Quaternion().setFromUnitVectors(J, vec);
        let geometry = geomUnitCylinder.clone();
        geometry.scale(1, h, 1);
        geometry.applyQuaternion(quaternion);
        geometry.translate(P.x, P.y, P.z);
        return geometry;
    }
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
1

For rotating the orientation, you can use

edge.rotateX(Math.PI / 2);

Or

edge.rotateY(Math.PI / 2);

Or

edge.rotateZ(Math.PI / 2);

As per the coordinates you wish to rotate. Hence, the code posted above with:

var orientation = new THREE.Matrix4();
/* THREE.Object3D().up (=Y) default orientation for all objects */
orientation.lookAt(pointX, pointY, new THREE.Object3D().up);
/* rotation around axis X by -90 degrees 
 * matches the default orientation Y 
 * with the orientation of looking Z */
orientation.multiply(new THREE.Matrix4(1,0,0,0,
                                       0,0,1,0, 
                                       0,-1,0,0,
                                       0,0,0,1));

Will not be required.

Anh Pham
  • 2,108
  • 9
  • 18
  • 29
0

Old question, but still relevant today. Building on Lee Stemkoski's answer, this is what i ended up getting to work:

const cylinderMesh = function( pointX, pointY )
{
    // edge from X to Y
    const direction = new THREE.Vector3().subVectors( pointY, pointX );
    const arrow = new THREE.ArrowHelper( direction.clone().normalize(), pointY );

    // cylinder: radiusAtTop, radiusAtBottom, 
    //     height, radiusSegments, heightSegments
    const edgeGeometry = new THREE.CylinderGeometry( 2, 2, direction.length(), 6, 4 );

    const edge = new THREE.Mesh( edgeGeometry, 
        new THREE.MeshBasicMaterial( { color: 0x0000ff } ) );
    edge.rotation = arrow.rotation.clone();
    edge.position = new THREE.Vector3().addVectors( pointX, direction.multiplyScalar(0.5) );
    return edge;
}

Note the main difference: you have to normalize the direction vector and take the origin to be pointY. Normalizing the direction vector requires a .clone() in order to allow you to use it later on in the position calculation

-1

I found that only one point can be matched, so I edit it( change pointX to pointY) as below and it works

edge.position = new
THREE.Vector3().addVectors(pointY,direction.multiplyScalar(0.5));
Haile
  • 3,120
  • 3
  • 24
  • 40
I JEN
  • 1