2

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.

Community
  • 1
  • 1
Lotan
  • 4,078
  • 1
  • 12
  • 30
  • Looks like you want to find the adjacent side for which is the distance where you can place the camera, user ```tan(a) = Opposite / Adjacent```, in terms of adjacent side it is ```Adjacent = tan(a) / Opposite``` I think this should work. Why you are calculating opposite side can't you just use the distance between black and yellow points – Ghost The Punisher Jan 31 '20 at 11:17
  • Thanks for your comment @Ankit but: 1-Adjacent = tan(a) / opposite still gives me incorrect values of adjacent side. 2-I don't understand your final point. If you are asking why i'm calculating the opposite side instead of grab it from the difference between points positions, look that my code is calculating it from black and yellow points. But if you are telling me that this can not be calculated like that...tell me how it can, please – Lotan Jan 31 '20 at 14:39
  • 1
    I realize that's my mistake, I didn't look at it close enough. I was like oh Mathf.Abs that's not a way to calculate the distance. you were just taking the difference of x-axis so that's totally right. – Ghost The Punisher Jan 31 '20 at 14:59
  • I think that [finding the angle based on the hypotenuse and the opposite side](https://www.mathsisfun.com/algebra/trig-finding-angle-right-triangle.html) make sure that you divide the camera's Fov by 2 as you did in your code. Try this and let me know. – Ghost The Punisher Jan 31 '20 at 15:05
  • The link you gave me does exactly what I'm doing, where is the difference with my approach? – Lotan Jan 31 '20 at 15:11
  • Try dividing camera FOV by 2 and use the distance between black and yellow points. Wait let me try it myself. – Ghost The Punisher Jan 31 '20 at 15:33
  • please include how you are using the return value to position the camera and also how you are changing the position of the yellow point to test the procedure. – Ruzihm Jan 31 '20 at 17:41
  • @Ruzihm as the camera object is positioned at 0, I simply add the return value to the gameObject.transform.position.z – Lotan Jan 31 '20 at 19:03
  • 1
    @Lotan Why would the camera be positioned at 0 make that a valid choice? That would mean that moving the yellow and black spot closer to the camera and change nothing else would not affect how the procedure positions the camera. That doesn't make any sense. You need to take the absolute position of the yellow and/or black point into account. You could instead find the position that's `result` distance from `blackPoint` in the backward direction, and set the gameObject to that position. `gameObject.transform.position = blackPoint.transform.position - result * cameraComp.transform.forward;` – Ruzihm Jan 31 '20 at 19:38
  • @Lotan Does that answer your question? – Ruzihm Feb 03 '20 at 16:13
  • 1
    Oh okey now I get it, than you Ruzihm! – Lotan Feb 04 '20 at 12:29

1 Answers1

1

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.

Ruzihm
  • 19,749
  • 5
  • 36
  • 48
  • Thanks @Ruzhim, your method works. I don't like the fact that doesn't really solve my doubt about "why my method does not work" (so if you know why, I'll appreciate an explanation about), but at least solves the objective. – Lotan Jan 31 '20 at 16:27
  • @Lotan It's hard to say because your calculation includes the localposition of the black point and the question doesn't say anything about its parent, so I have no idea what to make of that. – Ruzihm Jan 31 '20 at 16:34
  • well, it is the localPosition ,but it can also be the position itself, as the 2 spheres have no parent – Lotan Jan 31 '20 at 19:04