Finding the xyz coords of the viewport’s four corners

Hello

I have been struggling to do this please. More specifically if you had a point exactly where the camera is then camera.position will give you the xyz (world) coords of the camera and hence the point’s coords. What i want to do is to get the xyz (world) coords of the four corners of the viewport, something like viewport_topLeft.position that would return the three coords. Similarly for topRight, bottomLeft and bottomRight. And since the actual viewport is 2D, lets assume that the point at the topLeft is a point in the 3D space on the same plane as the camera (or at some known distance from the camera’s internal z-axis)
What I was thinking to do is:

  • calc the visible width and height of the viewport at some z depth

  • Draw a thin box-shaped mesh which always faces the camera and completely fills the viewport and

  • Calc the bounding box and from that I should be able to get the 4 corners and its coords in the world space.

I am stuck at the second bullet point. I think i can calculate the visible width and height at some distance from the camera, however when the camera is rotated that breaks down and I cannot fix it.

Can someone please help me solve this? This is how far I have got: https://jsbin.com/keducoxate/edit?html,css,output. If you rotate the camera and then press the “add viwport mask” you will see what I mean.

Is the way I outlined above a reasonable one, will it work or there is a much simpler one out there which I havent thought about?

Thanks in advance

@gmiliotis

My modification creates your box so it looks the same in respect to the current camera position and orientation. Then four spheres are added at the corners to illustrate how to get the global coordinates of the corners of the box.

Would this help a bit?

function viewport_box(dist){
    var x = 10; //visibleWidthAtZDepth(dist, camera)
    var y = 5;  //visibleHeightAtZDepth(dist, camera)

    fullBox = new THREE.Mesh(new THREE.BoxGeometry(x, y, 0.1), new THREE.MeshLambertMaterial({
      color: "blue"
    }));

    fullBox.rotation.copy( camera.rotation );
    fullBox.position.copy( camera.position );

    var v = new THREE.Vector3();
    camera.getWorldDirection( v )
    fullBox.position.addScaledVector( v, 10 )
          
    scene.add(fullBox)

    // get coordinates of corners
    fullBox.updateWorldMatrix( true, false );
      
    var cornerTopRight = new THREE.Mesh(
       new THREE.SphereGeometry(0.2),
       new THREE.MeshLambertMaterial({color:'white'})
    );
      
    var cornerTopLeft = cornerTopRight.clone();
    var cornerBottomRight = cornerTopRight.clone();
    var cornerBottomLeft = cornerTopRight.clone();

    cornerTopRight.position.copy( fullBox.localToWorld(new THREE.Vector3(x/2,y/2,0)) );
    cornerTopLeft.position.copy( fullBox.localToWorld(new THREE.Vector3(-x/2,y/2,0)) );
    cornerBottomRight.position.copy( fullBox.localToWorld(new THREE.Vector3(x/2,-y/2,0)) );
    cornerBottomLeft.position.copy( fullBox.localToWorld(new THREE.Vector3(-x/2,-y/2,0)) );
      
    scene.add( cornerTopRight, cornerTopLeft, cornerBottomRight, cornerBottomLeft );
}

– Pavel

PS: I think you do not need a box, the coordinates of corners might be calculated directly by the camera’s localToWorld.

2 Likes

This is really brilliant! Thanks so much! I think this is what I was looking for. So much for me to study in your script. It seems (sometimes, not always) I am calculating the visible width and height of the viewport wrong but this is an issue outside the scope of this question. I liked the way you are placing the object 10 units away from the camera. This is what the two lines below do right?

camera.getWorldDirection( v )
fullBox.position.addScaledVector( v, 10 )

I dont quite get the alternative way you are suggesting in your PS, using the the camera’s localToWorld to get the corners of the viewport directly. How I should go about that please? No need to code anything, just the main idea…

Thanks a million!

Yes –

That code places the blue object 10 units away from the camera. Currently the process is this: the blue object is placed along the camera direction (and it has the same orientation as the camera), and the global coordinates of the edges of the object are calculated from the local coordinates in respect to the object’s local coordinate system. My expectation is that you can calculate the global positions from local coordinates in respect to the camera’s local coordinate system, thus the blue object is not necessary and the function will be twice shorter.

– Pavel

Hi again. I am not sure if this what you mean. What I tried is to take, for example to top-left of the near plane (thats (-1, 1, -1) right?) and then unproject it using the camera projection matrix.

var v = new THREE.Vector3();
v.set(-1, 1, -1).unproject(camera); // that would give us the top left of the near plane in world coords, right?

I am getting something very close to your code but still I am struggling to fully understand it. For example using your code I get:

viewport_box(10)
.....
.....
......
cornerTopLeft.position
Vector3 {x: -3.3947912929075583, y: 5.773502691896257, z: -3.5352507957496894e-16}

where as I get:

vector.set( -1, 1, -1 ).unproject( camera );
Vector3 {x: -0.3394791292907559, y: 0.5773502691896264, z: 9}

My x,y are off by a factor of 10 while I have no idea about the z value. The factor of 10 is actually the input value to the viewport_box function. That seems to hold for any value. If for example you call viewport_box(123) then the coords of the cornerTopLeft are 123 times bigger than what you get from vector.set( -1, 1, -1 ).unproject( camera ). All that under the condition that I do not rotate the object. If I rotate what I say above do not hold. Maybe I need to update a matrix?

Is there any chance you know/explain what I am missing please? It will be a massive help.

Thanks in advance

@gmiliotis

I mean, instead of this line:

cornerTopRight.position.copy( fullBox.localToWorld(new THREE.Vector3(x/2,y/2,0)) );

it is possible to use:

cornerTopRight.position.copy( camera.localToWorld(new THREE.Vector3(x/2,y/2,-10)) );

Note that fullBox is not used, so it is not needed. The distance to the fullBox (that was 10) is now provided as negated z coordinate in Vector3(x/2,y/2,-10).

So, the full function is now:

function viewport_box(dist){
    var x = 10; 
    var y = 5;  
     
    var cornerTopRight = new THREE.Mesh(
       new THREE.SphereGeometry(0.2),
       new THREE.MeshLambertMaterial({color:'white'})
    );
      
    var cornerTopLeft = cornerTopRight.clone();
    var cornerBottomRight = cornerTopRight.clone();
    var cornerBottomLeft = cornerTopRight.clone();

    cornerTopRight.position.copy( camera.localToWorld(new THREE.Vector3(x/2,y/2,-10)) );
    cornerTopLeft.position.copy( camera.localToWorld(new THREE.Vector3(-x/2,y/2,-10)) );
    cornerBottomRight.position.copy( camera.localToWorld(new THREE.Vector3(x/2,-y/2,-10)) );
    cornerBottomLeft.position.copy( camera.localToWorld(new THREE.Vector3(-x/2,-y/2,-10)) );
      
    scene.add( cornerTopRight, cornerTopLeft, cornerBottomRight, cornerBottomLeft );
}

If you do not need the spheres, you can remove them too. The coordinates of a corner are given by:

camera.localToWorld(new THREE.Vector3(...,...,....))

– Pavel

Thanks so much, extremely helpful!

1 Like