Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Camera distance to fit a procedurally generated GameObject of varying dimensions?

Discussion in 'Scripting' started by andyastro, Sep 19, 2015.

  1. andyastro

    andyastro

    Joined:
    Sep 17, 2015
    Posts:
    16
    Dear all,

    In Unity with C#, for a 3D game, I want to calculate the minimum distance that my perspective camera has to be from a given GameObject (a procedurally generated mesh of random shape), so that the object is fully framed by the camera and leaving the least space possible around it in the screen. In other words, fully framed means that there is no part of the object outside the view area.

    I have found a few questions on the forum related to that sort of problem. Almost all of them, always aiming at 2D applications. I tried adapting these debates to the 3D wolrd, always without success. One of them pointed to this part of the official documentation: http://docs.unity3d.com/Manual/FrustumSizeAtDistance.html. I adapted that code to my situation, taking the sizes of the Bounds of my procedurally generated object into the calculation of the fustrum high. I also tried the same logic but with a slightly different formula (see http://xboxforums.create.msdn.com/forums/p/37168/215198.aspx). However, in neither case I was able to have enough precision. Sometimes the objects generated is big and the camera distance found is too short, sometimes it is too much.

    Here is my last code:

    Code (CSharp):
    1.  
    2. //The procedurally generated object was used to create the gobounder, an object which contains its bounding box
    3.             float frustumHeight= gobounder.size.y;
    4.             float distance = frustumHeight * 0.5F / Mathf.Tan(Camera.main.fieldOfView) * 0.5F * Mathf.Deg2Rad)
    5.             camera_currentposition = Camera.main.transform.position;
    6.             objectcenter= gobounder.center;
    7.             Camera.main.transform.position = new Vector3(objectcenter.x,objectcenter.y+distance,objectcenter.z);
    All results, as I said, were inconsistent. Just as a quick background, to make things easier, my camera does not need to rotate or move - it's fully static, with the exception of one axis to zoom (by moving the camera, not the FOV) in or out in order to frame the target object.

    Would anyone be kind enough to point me in the right direction with code snippets, suggestions, etc?
     
    jayconsystems likes this.
  2. andyastro

    andyastro

    Joined:
    Sep 17, 2015
    Posts:
    16
    I found an additional reference: http://forum.unity3d.com/threads/frame-camera-on-set-of-gameobjects.15844/

    It means that, so far, I have tried at least three different formulas for calculating the distance of the perspective camera:

    distance = frustumHeight * 0.5 / Mathf.Tan(camera.fieldOfView * 0.5 * Mathf.Deg2Rad);

    distance = (width * 0.5f) / (float)Math.Tan(camera.FieldOfView / 2);

    distance = (diagofboundingbox / 2) / Mathf.Abs(Mathf.Sin(Camera.fieldOfView*Mathf.Deg2Rad/2));

    None of them really work accurately. Small objects are framed well, but once the size of the objects increase, more and more space is left between objects and the edges of the screen, like it is shown in the picture below:



    Any ideas on how to tweak the formula?
     
  3. ConstMe

    ConstMe

    Joined:
    Aug 9, 2019
    Posts:
    1
    This is relatively hard to do. My implementation is about 4 pages of C++ code. I can’t share the code, but I can share the ideas.

    Initialize perspective projection matrix, i.e. setup FOV.
    Initialize "look at" view matrix, so the camera looks from some direction into the center of your bounding box.
    Multiply matrices to make view*projection.

    Use code from this article to extract left, top, right and bottom clipping planes from the view*projection matrix.
    Normalize these planes, i.e. multiply all 4 coordinates by 1.0 / (x^2 + y^2 + z^2).

    Create a float array `planeDistances` with 4 elements equal to FLT_MAX (C++) or float.MaxValue (C#).

    Now iterate over the 8 vertices of the box.

    For each vertex, compute signed distance to every clipping plane. I’m using C++ so calling XMPlaneDotCoord but the formula is trivial, very likely Unity has an equivalent, the comment says // Result = P[0] * V[0] + P[1] * V[1] + P[2] * V[2] + P[3]. Update that float array, accumulating minimum value for each plane.

    You now have 4 float values, each one is a signed distance between corresponding clipping plane, and the outermost vertex of the box.

    Iterate over 4 clipping planes. Shift each plane so it touches the bounding box from the outside, to do that, just subtract planeDistances from plane.w. Intersect the shifted plane with the camera’s axis, and find the farthest point from the object.

    You now know where to position the camera so it touches one of the cube’s vertices, and the whole cube is inside the frustum.
     
  4. jayconsystems

    jayconsystems

    Joined:
    May 14, 2019
    Posts:
    4
  5. niuage

    niuage

    Joined:
    Nov 17, 2019
    Posts:
    88
    This works perfectly for me:

    Code (CSharp):
    1. Renderer renderer = yourGameObject.GetComponent<Renderer>();
    2. Bounds bounds = renderer.bounds;
    3.  
    4. var distanceNeededToFitWidth = bounds.size.x * 0.5f / Mathf.Tan(camera.fieldOfView * camera.aspect * 0.5f * Mathf.Deg2Rad);
    5. var distanceNeededToFitHeight = bounds.size.y * 0.5f / Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
    6.  
    7. Vector3 center = camera.transform.position + camera.transform.forward * Mathf.Max(distanceNeededToFitWidth, distanceNeededToFitHeight);
    8.  
    9. Vector3 desiredPosition = center -
    10.     camera.transform.up * bounds.extents.y -
    11.     camera.transform.right * bounds.extents.x;
    12.  
    13. yourGameObject.transform.position = desiredPosition;
    The `desiredPosition` depending on bounds.extents is only necessary if the origin of your mesh is not in the middle. You might need to adapt that part depending on your origin point.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,762
    While we're necro-ing eight (8) year old posts, beware that Cinemachine has come along in the interim and already does ALL of these things for you: keeping X amount of objects in view, etc.

    Camera stuff is pretty tricky... you may wish to consider using Cinemachine from the Unity Package Manager.

    There's even a dedicated forum: https://forum.unity.com/forums/cinemachine.136/
     
  7. niuage

    niuage

    Joined:
    Nov 17, 2019
    Posts:
    88
    Def a good point :) I'm just using that to frame an object in a second camera to display in the UI so I feel like cinemachine might be overkill in my case when this script works.