0
\$\begingroup\$

Basically I have an orthographic camera that is looking at a simple square. The camera is sitting above the square and tilted by x: 45° and y: 45°, so the camera sees the square as a rhombus / diamond shape.

I want the camera to be moveable within a rectangular boundary that appears to be leveled with the camera, so it would need to be rotated by 45° around the y-axis (up-vector in Unity) in World-coordinates. I already figured out the math to constrain the camera to this shape, but there is a weird thing going on when the movement gets clipped: The camera doesn't follow the outline perfectly, but in a rounded shape (see screenshot). Could this be due to a rounding error? Code snipped below. The red outline shows how the camera actually moves.

using System;
using UnityEngine;
using static UnityEngine.Mathf;

public class InputController : MonoBehaviour
{
    private const float CamHeight = 10f;
    [SerializeField] private GameObject gameBoard;
    [SerializeField] private GameObject camBounds;
    [SerializeField] private float camBoundRadius = 5f;
    private Vector3 _camOrigin;
    private MeshCollider _collider;
    private Vector3 _difference; // Change in position of mouse relative to origin
    private Camera _mainCamera;
    private Vector3 _origin; // Place where mouse is first pressed
    private Plane _worldPlane;

    private void Start()
    {
        _mainCamera = Camera.main;
        if (_mainCamera is null) throw new Exception("No main camera could be found!");
        // Set the camera so that the board is always in the center
        _camOrigin = new Vector3(
            -CamHeight / Sqrt(2f),
            CamHeight,
            -CamHeight / Sqrt(2f));
        _mainCamera.transform.position = _camOrigin;
        _collider = gameBoard.GetComponent<MeshCollider>();
        _worldPlane = new Plane(Vector3.up, 0); // For dragging the camera around
    }

    private void LateUpdate()
    {
        if (Input.GetMouseButtonDown(0)) _origin = MouseWorldPosition();
        if (Input.GetMouseButton(0))
        {
            _difference = MouseWorldPosition() - transform.position;
            var newPos = _origin - _difference;
            var absDiff = newPos - _camOrigin;

            var angle = Angle2Pi(Vector3.right, absDiff);
            var constraint = SawTooth(angle);

            if (absDiff.magnitude < constraint)
                transform.position = newPos;
            else
                transform.position = _camOrigin + absDiff.normalized * constraint;
        }
    }

    private Vector3 MouseWorldPosition()
    {
        var clickedPoint = Input.mousePosition;
        var ray = _mainCamera.ScreenPointToRay(clickedPoint);
        var worldPos = Vector3.zero;

        if (_worldPlane.Raycast(ray, out var raycastHit)) worldPos = ray.GetPoint(raycastHit);

        return worldPos;
    }

    private float SawTooth(float theta)
    {
        var x = 4f / PI * camBoundRadius * (1f - 1f / Sqrt(2f));
        x *= Abs(theta % (PI / 2f) - PI / 4f);
        x += camBoundRadius / Sqrt(2f);
        return x;
    }

    private static float Angle2Pi(Vector3 from, Vector3 to, Vector3 normal = default)
    {
        if (normal == default) normal = Vector3.up;

        var angle = Vector3.Angle(from, to) * (PI / 180f);
        var sign = Sign(Vector3.Dot(normal, Vector3.Cross(from, to)));
        angle = 2f * PI - angle * sign;

        return angle;
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Well I took a different approach that works perfectly and actually makes use of the bounding box shown in the screenshot. Here the code:

private void LateUpdate()
{
    if (Input.GetMouseButtonDown(0)) _origin = MouseWorldPosition();
    if (Input.GetMouseButton(0))
    {
        _difference = MouseWorldPosition() - transform.position;
        var newPos = _origin - _difference;
        var absDiff = newPos - _camOrigin;

        if (absDiff == Vector3.zero) return;

        // Check if newPos is outside the bounding box, i.e. if a raycast hits the bounding box
        var ray = new Ray(newPos, -absDiff.normalized);
        if (_camBounds.Raycast(ray, out var raycastHit, absDiff.magnitude))
            transform.position = ray.GetPoint(raycastHit.distance);
        else
            transform.position = newPos;
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .