Warm tip: This article is reproduced from stackoverflow.com, please click
c# unity3d camera trigonometry fieldofview

Maximize zoom distance to fit 2 objects

发布于 2020-03-28 23:16:08

Objective: Move the camera position ONLY on the Z axis so the frustrum fit 2 objects.

Conditions:

  • One of the objects will be allways aligned with the camera X position
  • Camera is set on perspective mode, not ortographic.
  • the 2 spheres have no parent

The result seen in ortographic mode from top-view should look like this: enter image description here

What I've done so far:

Using trigonometry this can be seen it as:

enter image description here

Knowing that, the objective is to find the Adjacent side, which will be the distance between the camera and the black point that will still fit the yellow one.

TECHNICALLY this piece of code should find the adjacent value:

private float CalculateMaxZoomDistanceToBall()
{
    //Calculate angle from camera, should be divided of 2 cause it's placed on the middle of the line
    Camera currentCamera = cameraComp;
    angleDegrees = currentCamera.fieldOfView / 2; //(degrees)

    //pass the angle to radians 
    angleRadians = angleDegrees * Mathf.Deg2Rad;

    //Calculate the SinAngle
    sinAngle = Mathf.Sin(angleRadians);

    //Calculate Opposite       
    opposite = Mathf.Abs(blackPoint.transform.localPosition.x - yellowPoint.transform.position.x);

    //Calculate hypotenuse
    hypotenuse = opposite / sinAngle;

    //Calculate CosX
    cosAngle = Mathf.Cos(angleRadians);

    //Calculate adjacent distance
    adjacent = cosAngle * hypotenuse;

    return adjacent;
}

as the camera object is positioned at 0, I simply add the return value to the gameObject.transform.position.z

And someone could say "but this is looking for the vertical FOV, you need the horizontal one", okey, I've also tried with the horizontal one, finded with:

float vFOVrad = currentCamera.fieldOfView * Mathf.Deg2Rad; 
float cameraHeightAt1 = Mathf.Tan(vFOVrad * 0.5f);
float hFOVrad = Mathf.Atan(cameraHeightAt1 * currentCamera.aspect) * 2;
hFOV = hFOVrad * Mathf.Rad2Deg;

And it's not working, in some cases the camera position is to far of the espected position, sometimes it fits well and others it just goes to close.

Any help will be apreciated, thank you.

Questioner
Lotan
Viewed
73
Ruzihm 2020-02-01 00:14

I would avoid working with angles and work in the world of vectors and planes.

Determine which side of the camera the yellow point is on:

Camera cam = cameraComp;
Transform camTransform = cam.transform;
Vector3 yellowPos = yellowPoint.transform.position;

// <0 if on left, >0 if on right
float camDirection = Vector3.Dot(camTransform.right, yellowPos - camTransform.position);

// if it's directly straight ahead, do nothing.
if (Mathf.Approximately(camDirection, 0f)) return;

Find a ray for the camera viewport edge on the same side of the yellow point. Height in the viewport won't matter.

Ray edgeRay = cam.ViewportPointToRay(camDirection < 0f ? Vector3.zero : Vector3.right);

Define an algebraic plane (not physics plane) normal to the camera's right and going through the position of the yellow point:

Plane yellowPlane = new Plane(camTransform.right, yellowPos);

Find the intersection of the ray and plane using algebraic raycast (not physics raycast):

float raycastDistance;
if (! yellowPlane.Raycast(edgeRay, out raycastDistance)) return; // should not return

Vector3 raycastPoint = edgeRay.GetPoint(raycastDistance);

Find the difference from the intersection point to the yellowPoint position, and do a dot product with the camera's forward direction to find how to move the camera along its forward direction:

float forwardDelta = Vector3.Dot(camTransform.forward, yellowPos - raycastPoint);

camTransform.Translate(0f, 0f, forwardDelta);

So, altogether:

Camera cam = cameraComp;
Transform camTransform = cam.transform;
Vector3 yellowPos = yellowPoint.transform.position;

// <0 if on left, >0 if on right
float camDirection = Vector3.Dot(camTransform.right, yellowPos - camTransform.position);

// if it's directly straight ahead, do nothing.
if (Mathf.Approximately(camDirection, 0f)) return;

Ray edgeRay = cam.ViewportPointToRay(camDirection < 0f ? Vector3.zero : Vector3.right);

Plane yellowPlane = new Plane(camTransform.right, yellowPos);

float raycastDistance;
if (! yellowPlane.Raycast(edgeRay, out raycastDistance)) return; // should not return

Vector3 raycastPoint = edgeRay.GetPoint(raycastDistance);

float forwardDelta = Vector3.Dot(camTransform.forward, yellowPos - raycastPoint);

camTransform.Translate(0f, 0f, forwardDelta);

The good thing about this approach is that it will work regardless of the orientation of the camera, or the relative position of the point from the camera.