Search code examples
matrix3dthree.jsrotational-matrices

three.js: rotational matrix to place THREE.group along new axis


(Please also refer to my illustration of the problem: https://i.sstatic.net/SfwwP.png)

problem description and ideas

I am creating several objects in the standard XYZ coordinate system. Those are added to a THREE.group.

Please think of the group as a wall with several frames and image hung on it. I want to create my frame objects with eg. dimension of (40, 20, 0.5). So I get a rather flat landscape formatted frame/artwork. I create and place several of those. Then I add them to the group, which I wanted to freely rotate in the world along two vectors start and end.

The problem I am struggling with is how to rotate and position the group from a given vector start to a give vector end.

So far I tried to solve it with a THREE.Matrix4().lookAt :

var group = new THREE.Group();
startVec = new THREE.Vector3( 100, 0, -100 );
endVec = new THREE.Vector3( -200, 0, 200 );
matrix = new THREE.Matrix4().lookAt(startVec, endVec, new THREE.Vector3( 0, 1, 0 ));
group.matrixAutoUpdate = false;

var object1 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 20, 40), mat);
// etc. -> notice the swapping of X and Z coordinates I have to do.
group.add(object1);

group.applyMatrix(matrix);

You can see the example on jsfiddle: http://jsfiddle.net/y6b9Lumw/1/

If you open jsfiddle, you can see that the objects are not placed along the line from start to end, although I their are placed along the groups internal X-Axis like: addBox(new THREE.Vector3( i * 30, 0 , 0 ));

Full code:

<html>
<head>
<title>testing a rotation matrix</title>
 <style>body { margin: 0; } canvas { width: 100%; height: 100% } </style>
</head> <body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<script>

var scene, camera, renderer, light, matrix;
var startVec, endVec;
var boxes;


function addBox(v) {
    var boxmesh;
    var boxgeom = new THREE.BoxGeometry( 15, 5, 1 );
    var boxmaterial = new THREE.MeshLambertMaterial( {color: 0xdd2222} );
    boxmesh = new THREE.Mesh( boxgeom, boxmaterial );

    //boxmesh.matrix.makeRotationY(Math.PI / 2);
    boxmesh.matrix.setPosition(v);

    boxmesh.matrixAutoUpdate = false;

    boxes.add(boxmesh);
}
function init() {
    renderer = new THREE.WebGLRenderer();
                    renderer.setClearColor( 0x222222 );
                    renderer.setPixelRatio( window.devicePixelRatio );
                    renderer.setSize( window.innerWidth, window.innerHeight );
                    document.body.appendChild( renderer.domElement );

    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( -20, 30, 300 );

    var light = new THREE.PointLight (0xCCCCCC, 0.5 );
    scene.add(light);

    startVec = new THREE.Vector3( 100, 0, -100 );
    endVec = new THREE.Vector3( -200, 0, 200 );
    matrix = new THREE.Matrix4().lookAt(startVec, endVec, new THREE.Vector3( 0, 1, 0 ));

    boxes   = new THREE.Group();


    for (var i = -100; i <  100; i++) {
        addBox(new THREE.Vector3( i * 30, 0 , 0 ));
    }

    boxes.matrixAutoUpdate = false;
    boxes.applyMatrix(matrix);
    scene.add(boxes);



    var linegeometry = new THREE.Geometry();
    linegeometry.vertices.push( startVec, endVec);
    var line = new THREE.Line(linegeometry, new THREE.LineBasicMaterial({color: 0x33eeef}));
    scene.add(line);


    render();
}

function render(){

  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
init();



</script>
</body> </html>

This works only nicely to some extend. As the look vector is usually oriented along the Z-Axis (i think it is (0,0,1)). So unfortunately the objects inside the group get rotated like that aswell. This is actually what you would expect from a lookAt() rotational transformation. It's not what I would like to have though, as this places all the children in the group, on their Z-Axis, instead of their X-Axis.

In order to have things look properly I had to initialize my groups children with X and Z swapped. Instead of:

var object1 = new THREE.BoxGeometry( 40, 20, 0.5 );

I have to do:

var object1 = new THREE.BoxGeometry( 0.5, 20, 40 );

same if I want to translate objects in the group on the X-Axis, I have to use the Z-Axis, as that is the look-vector along which the whole wall is oriented by the matrix transformation.

my question is: How does my matrix have to be constructed/look like to accomplish what I want: Normally create objects, and then have their X-Axis placed along vector start and vector end, like placing artworks on a wall, which can be moved around? I thought about creating a matrix, whose X-Axis is end.sub(start), so the vector from start end end, might this be what I need to do? If so, how would I construct it?

problem illustration with an image

I tried to illustrate my sitation in two images. One being the wall, one being the wall inside the world, with the same objects attached to the wall (see top of the post).

In the first figure you see the local coordinate system of the group, with two added children, one translated along X. In the second figure, you can see the same localsystem inside the world how I would like it to be. The green axes are the world axes. The start and end vectors are shown aswell. You can see, both boxes, are properly placed along that line.


Solution

  • I would like to answer my own question by disregarding the idea of manipulating the matrix myself. Thx to @WestLangley I adapted my idea to the following by setting the groups quaternion via .setFromUnitVectors. So the rotation is derived from the rotation from the x-axis to the direction vector of start and end, as explained in three.js' documentation:

    "Sets this quaternion to the rotation required to rotate direction vector vFrom to direction vector vTo." (http://threejs.org/docs/#Reference/Math/Quaternion.setFromUnitVectors)

    Below is the relevant part of my solution:

    // define the starting and ending vector of the wall
    start = new THREE.Vector3( -130, -40, 300 );
    end = new THREE.Vector3( 60, 20, -100 );
    
    // dir is the direction from start to end, normalized
    var dir = new THREE.Vector3().copy(end).sub(start).normalize();
    
    // position wall in the middle of start and end
    var middle = new THREE.Vector3().copy(start).lerp(end, 0.5);
    wall.position.copy(middle);
    
    // rotate wall by applying rotation from X-Axis to dir
    wall.quaternion.setFromUnitVectors( new THREE.Vector3(1, 0, 0), dir );
    

    The result can be seen here: http://jsfiddle.net/L9dmqqvy/1/