George Zyn
George Zyn

Reputation: 93

Zooming out the camera so that it can see all objects

I have a list of objects, these are blue stickmen on the video, I need to make the camera move away by itself and all objects (blue stickmen) always fit into it, you need to take into account that there will be more and more objects each time, so the camera should be dynamic and adapt itself to all objects

https://youtube.com/shorts/x3uSO2L22Kc?feature=share

Upvotes: 1

Views: 147

Answers (2)

Mr. For Example
Mr. For Example

Reputation: 4313

Practical solution for any camera angle and objects positions

Showcase

The idea here is fairly simple.

At each step we check if each object is inside of camera view or is camera too far away, then we simply adjust the camera position towards a better one.

Step by step, our camera will follow target objects dynamically, and when stabilized, all target objects will be captured by camera.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraAutoFitSmooth : MonoBehaviour
{
    public int maxNumberOfObjs = 100;
    public GameObject objPrefab;
    public float maxInitRange = 10f;

    public float minCameraHeight = 1f;
    public float maxCameraMoveSpeed = 9f;
    public float marginalPos = 0.1f;

    [HideInInspector]
    public List<Transform> objs = new List<Transform>();
    Camera cam;

    void Start()
    {
        cam = Camera.main;
    }

    void Update() 
    {
        if (Input.GetKeyDown(KeyCode.Space))
            RandomObjs();
    }

    // Randomly regenerate objects
    void RandomObjs()
    {
        int nowNumberOfObjs = Random.Range(0, maxNumberOfObjs);
        for (int i = objs.Count - 1; i > nowNumberOfObjs; i--)
        {
            Destroy(objs[i].gameObject);
            objs.RemoveAt(i);
        }

        for (int i = objs.Count; i <= nowNumberOfObjs; i++)
            objs.Add(Instantiate(objPrefab).transform);
            
        foreach (var obj in objs)
            obj.position = Random.insideUnitSphere * maxInitRange;
    }

    void LateUpdate()
    {
        SetCameraFitPosition(Time.deltaTime);
    }

    void SetCameraFitPosition(float deltaTime)
    {
        Vector3 targetCamPos = cam.transform.position;
        if (objs.Count == 1)
        {
            targetCamPos = objs[0].position - minCameraHeight * cam.transform.forward;
        }
        else if (objs.Count > 1)
        {
            float minInsideDiff = 1f, maxOutsideDiff = 0f;
            Vector3 center = Vector3.zero;
            foreach (var obj in objs)
            {
                Vector3 screenPos = GetScreenPos(obj.position);

                if (IsInsideView(screenPos))
                    minInsideDiff = Mathf.Min(minInsideDiff, CalculateInsideDiff(screenPos.x), CalculateInsideDiff(screenPos.y), CalculateInsideDiff(screenPos.z));
                else
                    maxOutsideDiff = Mathf.Max(maxOutsideDiff, CalculateOutsideDiff(screenPos.x), CalculateOutsideDiff(screenPos.y), CalculateOutsideDiff(screenPos.z));

                center += obj.position;
            }
            center /= objs.Count;

            float nowHeight = Vector3.Project(cam.transform.position - center, cam.transform.forward).magnitude;
            float maxDiff = maxOutsideDiff > 0f ? maxOutsideDiff : -minInsideDiff;
            float finalHeight = Mathf.Max(nowHeight + maxDiff * maxCameraMoveSpeed, minCameraHeight);

            targetCamPos = center - finalHeight * cam.transform.forward;
        }

        cam.transform.position = Vector3.MoveTowards(cam.transform.position, targetCamPos, maxCameraMoveSpeed * deltaTime);
    }

    Vector3 GetScreenPos(Vector3 pos)
    {
        return cam.WorldToViewportPoint(pos);
    }
    float CalculateOutsideDiff(float pos)
    {
        float diff = 0f;
        if (pos > 1f + marginalPos)
            diff = pos - 1f;
        else if (pos < -marginalPos)
            diff = -pos;
        return diff;
    }
    float CalculateInsideDiff(float pos)
    {
        float diff = 0f;
        if (pos < 1f - marginalPos && pos > marginalPos)
            diff = Mathf.Min(1f - pos, pos);
        return diff;
    }
    bool IsInsideView(Vector3 screenPoint)
    {
        return screenPoint.z > 0f && screenPoint.x > 0f && screenPoint.x < 1 && screenPoint.y > 0f && screenPoint.y < 1;
    }
}

If you need more info feel free to contact me :) Cheers!

Upvotes: 1

Voidsay
Voidsay

Reputation: 1550

The following script will position a perspective camera in a top down view, so that all tracked GameObjects (objs) are visible.

It is assumed that the objects are on the zero xz plane and are points, so their actual dimensions are not taken into account. There must be at least one tracked object. The objects may not be spaced in such a way that would require the cameras height to exceed the maximum floating point value.

public GameObject[] objs;//objects that must be fitted
private Camera cam;
float recXmin, recXmax, recYmin, recYmax;
Vector3 center;

void Start()
{
  cam = Camera.main;
  cam.transform.rotation = Quaternion.Euler(90, 0, 0);
}

void LateUpdate()
{
  recXmin = objs[0].transform.position.x;
  recXmax = objs[0].transform.position.x;
  recYmin = objs[0].transform.position.z;
  recYmax = objs[0].transform.position.z;
  center = Vector3.zero;

  foreach (GameObject obj in objs)
  {
    if (obj.transform.position.x < recXmin)
    {
    recXmin = obj.transform.position.x;
    }
    if (obj.transform.position.x > recXmax)
    {
    recXmax = obj.transform.position.x;
    }
    if (obj.transform.position.z < recYmin)
    {
    recYmin = obj.transform.position.z;
    }
    if (obj.transform.position.z > recYmax)
    {
    recYmax = obj.transform.position.z;
    }
  }

  float horizontalHeight = (recYmax - recYmin) / 2 / Mathf.Tan(Mathf.Deg2Rad * cam.fieldOfView / 2);
  float verticalHeight = (recXmax - recXmin) / 2 / Mathf.Tan(Mathf.Deg2Rad * Camera.VerticalToHorizontalFieldOfView(cam.fieldOfView, cam.aspect) / 2);
  float finalHeight = horizontalHeight > verticalHeight ? horizontalHeight : verticalHeight;
  center = new Vector3(recXmin + (recXmax - recXmin) / 2, finalHeight, recYmin + (recYmax - recYmin) / 2);
  cam.transform.position = center;
}

void OnDrawGizmos()
{
  Gizmos.color = Color.red;
  Gizmos.DrawLine(new Vector3(recXmin, 0, recYmin), new Vector3(recXmin, 0, recYmax));
  Gizmos.DrawLine(new Vector3(recXmax, 0, recYmin), new Vector3(recXmax, 0, recYmax));

  Gizmos.color = Color.green;
  Gizmos.DrawLine(new Vector3(recXmin, 0, recYmin), new Vector3(recXmax, 0, recYmin));
  Gizmos.DrawLine(new Vector3(recXmin, 0, recYmax), new Vector3(recXmax, 0, recYmax));

  Gizmos.color = Color.blue;
  Gizmos.DrawSphere(center, 0.5f);
}
  • the script determines the "bounding square" formed by all tracked objects
  • the bounding square is assumed to be the basis of a pyramid with the camera at its peak
  • using trigonometry the height of the camera can be calculated, by taking into account the known length of the pyramid's base side and the cameras field of view
  • the calculation is made twice for the cameras horizontal and vertical field of view
  • the greater of these two values is then selected
  • lastly the camera is position into the middle of the pyramids base and at the determined height, so that it ends up at the pyramids peak

Upvotes: 1

Related Questions