How to calculate the angles of a given orthographic camera perspective

anglegeometrytrigonometry

In Blender, I can set up an orthographic / isometric camera like so:

enter image description here

With the X, Y, and Z rotation set to those values, a cube has the following properties: all sides are rendered at equal length, and the lines are at perfect 30deg (or 120deg) angles from each other. In other words, if you zoomed into the corner of the square and measured the angles of all three lines going outward, one would be going straight up, and the other two would be 30deg off horizontal, like so:

enter image description here

So, my question is two-fold, I guess, because I want to be able to do this in 2 ways:

  1. If I know the rotation of the camera in those X and Z degress in the first picture (assume Y is a constant 0), how can I calculate the resultant angles – those two angles I marked out as 30deg in the second picture? AND how can I calculate the proportional lengths of each side of a rendered square?

  2. Basically the reverse of the above – say I just know the angles from the second picture, and I want to find out the camera angle to match – how can I do that?

Are there simple forumlas for these questions?

Best Answer

I actually found the answer. Or rather, kind of invented it.

The first step is to create your 1-unit "room", that hollowed out cube that you see in the question, which is 1 unit tall, 1 unit wide in the x direction, and -1 unit wide in the y direction (which is just the default way blender scenes are set up).

First, I had the idea that I was going to project that view onto a plane that matches the Camera's angle, and then figure out everything I want to know either from that plane, or by then converting all the points on that plane to a plane that's just pointing straight down.

But then I figured out that it's probably easier to actually convert the x,y,z vectors of the Unit Room we set up to be viewed as if they were being viewed from above - so rather than making a projection on to the rotated camera, and then unrotating the projection, what I do is I unrotate EVERYTHING - camera and the room, and then the projection kinda takes care of itself.

So I used this guide to figure out how to "unrotate" the x,y,z vectors of the unit room, and I implemented that. Then, as I was working on figuring out how to project that onto a plane facing straight downwards, I realized -- that's super easy to do! Just take the (x,y) coordinates from the previous step and ignore the z-coordinates.

So at that point, I have 4 points in 2 dimensions - the center point, the point at the end of the z-vector, the point at the end of the y-vector, and the point at the end of the x-vector. Using those points, now only in 2 dimensions, it's actually rather straightforward to calculate the angles between the vectors and the proportional lengths of them as projected onto the camera.

I wrote all my code out and saved it here: https://jsfiddle.net/3aqew0c9/3/

// https://math.stackexchange.com/questions/100439/determine-where-a-vector-will-intersect-a-plane
// https://stackoverflow.com/questions/14607640/rotating-a-vector-in-3d-space

function findAngles(xDeg, zDeg) {
    const startingVectors = {
    x: [1,0,0],
    y: [0,-1,0],
    z: [0,0,1]
  };
  
  // step 1
  // the first goal is to rotate those starting vectors by -zDeg, -xDeg
  const newVectors = {
        x: rotateVectorX(-xDeg, rotateVectorZ(-zDeg, startingVectors.x)),// rotateVectorZ(-zDeg,rotateVectorX(-xDeg, startingVectors.x)),
        y: rotateVectorX(-xDeg,rotateVectorZ(-zDeg, startingVectors.y)) ,
        z: rotateVectorX(-xDeg,rotateVectorZ(-zDeg, startingVectors.z))
  };
  
  $('td#newxv').text(JSON.stringify(newVectors.x));
  $('td#newyv').text(JSON.stringify(newVectors.y));
  $('td#newzv').text(JSON.stringify(newVectors.z));
  
  // step 2: project new vectors onto downward-pointing plane
  // vector that defines downward pointing plane is [0,0,-1]
  // https://math.stackexchange.com/questions/100439/determine-where-a-vector-will-intersect-a-plane
  // turns out, the way to project upward toward a downward-pointing plane is to just ignore the z-component of the newVectors, and use the x and ys, with 0,0 as center
  
  $('td#xproj').text(`(${newVectors.x[0]}, ${newVectors.x[1]})`);
  $('td#yproj').text(`(${newVectors.y[0]}, ${newVectors.y[1]})`);
  $('td#zproj').text(`(${newVectors.z[0]}, ${newVectors.z[1]})`);
  
  // step 3: calculate the angles and length
  const xangle = Math.atan(newVectors.x[1] / newVectors.x[0]) * 180/ Math.PI
  $('#xangle').text(xangle + '°');
  const yangle = Math.atan(newVectors.y[1] / newVectors.y[0]) * 180/ Math.PI
  $('#yangle').text(yangle + '°');
  // z is always straight up so don't bother calculating this
  // if you wanted to, i guess you would put -90°
  
  // now lengths
  const xlength = (newVectors.x[0] ** 2 + newVectors.x[1] ** 2 ) ** 0.5;
  const ylength = (newVectors.y[0] ** 2 + newVectors.y[1] ** 2 ) ** 0.5;
  const zlength = (newVectors.z[0] ** 2 + newVectors.z[1] ** 2 ) ** 0.5;
  $('#xlength').text(xlength);
  $('#ylength').text(ylength);
  $('#zlength').text(zlength);
  
  // normalize it by setting the longest length to 1
  // first find max length
  const maxLength = Math.max(xlength, ylength, zlength);
  const multiplier = 1 / maxLength;
  const nxlength = xlength * multiplier;
  const nylength = ylength * multiplier;
  const nzlength = zlength * multiplier;
  $('#nxlength').text(nxlength);
  $('#nylength').text(nylength);
  $('#nzlength').text(nzlength);
}

// rotating vectors described here
// https://stackoverflow.com/questions/14607640/rotating-a-vector-in-3d-space
function rotateVectorX(deg, vec) {
    // vector is an array [x,y,z]
  const answerVector = [vec[0], null, null]; // x is the same, we need to calculate y and z
  // y = y cos θ − z sin θ
  answerVector[1] = vec[1] * Math.cos(dtr(deg)) - vec[2] * Math.sin(dtr(deg));
  // z = y sin θ + z cos θ
  answerVector[2] = vec[1] * Math.sin(dtr(deg)) + vec[2] * Math.cos(dtr(deg));
  return answerVector;
}

function rotateVectorZ(deg, vec) {
    // vector is an array [x,y,z]
  const answerVector = [null, null, vec[2]]; // z is the same, we need to calculate x and y
  // x = x cos θ − y sin θ
  answerVector[0] = vec[0] * Math.cos(dtr(deg)) - vec[1] * Math.sin(dtr(deg));
  // y = x sin θ + y cos θ
  answerVector[1] = vec[0] * Math.sin(dtr(deg)) + vec[1] * Math.cos(dtr(deg));
  return answerVector;
}

// dtr means convert degrees to radians
function dtr(deg) {
    return deg*Math.PI/180;
}

findAngles(54.736, 45);

I wrote a bit about it here, but I'm going to make a UI for it as well.