# How can I calculate where my camera can be to avoid showing something outside a region?

I have a landscape model, which goes from the coordinate -45, -45 to 90, 90.

The edge of the model just cuts off and i would like to somehow stop the screen from ever passing these points. Basically sticking a boundary so that you can't see off the side. The game is always in top view, with the camera directly facing down.

However the problem is that the player can zoom in and out, obviously you have a wider range of view when zoomed out. (There is a limit to how far you can zoom out, you couldn't zoom out say, so that opposite sides of the map are seen at once).

I am assuming using something like Viewport.Project or Unproject, which changes screen coordinates to 3d space and the other way round, could somehow help me in the situation.

My initial idea may lag too much, which would be a while loop saying "While projection of screen point.X is less that -45, add 1 to the camera.X" etc.

Does anyone have any ideas for this? To sum it up, i need to find a way of stopping anything outside the area of -45, -45 to 90, 90 from being shown on the screen.

Thanks for any help!

• without actually making the code yet, I'd say you should use trigonometry to work out the maximum distance from the ground you can be before you see the edge. Dec 22, 2011 at 15:20 // 'Camera' is an object containing the position of the camera
// 'A' is the angle shown in the picture above
// wallHeight is an optional variable giving the height of a surrounding wall that you could not see through

/ Math.Cos(MathHelper.ToRadians(A));// Here A is the angle 'A' in the image above

float c = wallHeight;// Not necessary, but if you have a wall around the map you can go higher.

// x is the point furthest away from the center
float x = ( Math.Abs(Camera.X) > Math.Abs(Camera.Z) ? Camera.X : Camera.Y );

// y = mx + c : x = (y - c) / m     You can also make use of the second formula later if you need to reverse the process.
float y = m * x + c;


This code (which needs optimizing) will give you the maximum height you can go according to the x and z coordinates of the camera.

• Unfortunately it is the boundaries that i am after, not the camera.Y i need to find out. Thanks anyway, you helped me do a little equation to find the highest possible camera position!
– Joel
Dec 23, 2011 at 1:50
• If you supply my code with the x and z coordinates of the camera it will find the maximum Y coordinate For those coordinates. so if you check whether the camera Y is less than the result, you can check whether you need to change the y coordinate, and to what. Dec 23, 2011 at 2:18

I wrote something just for this a while ago, but for a 2D camera. I haven't tried adapting it to use a 3D camera, but since your game is always top down, I think it can be considered as a 2D camera too, so the concepts should be similar enough. Hope it's useful:

It's basically a way to extend your usual Camera class in order to add Rectangle Limits property that specifies a region that the player can never seen beyond. This is done by validating the camera's Position and Zoom values whenever they change in accordance with the Limits property.

Start by reading the article for the entire solution, download the sample at the end and play with it. See how it could be adapted to your project. It's relatively simple, just two new methods and a few property changes.

As usual, for future reference, I'll leave the most relevant bits here too:

Step 1)

Add a Rectangle? Limits property to the Camera class:

public interface Camera
{
public Viewport Viewport { get; }
public Vector2 Position { get; set; }
public float Zoom { get; set; }
public Matrix ViewMatrix { get; }
public Rectangle? Limits { set; }
}


Step 2)

Create methods to validate Zoom and Position of the camera. This is where you might need to change something to work with a 3D camera, so I'll also leave a general description:

private void ValidateZoom()
{
if (_limits.HasValue)
{
float minZoomX = (float)_viewport.Width / _limits.Value.Width;
float minZoomY = (float)_viewport.Height / _limits.Value.Height;
_zoom = MathHelper.Max(_zoom, MathHelper.Max(minZoomX, minZoomY));
}
}


Validating Zoom is all about the camera's "size", the extent that it can see. You basically find out what is the smallest amount of zoom the camera can have before it sees too much, and clamp it there.

private void ValidatePosition()
{
if(_limits.HasValue)
{
Vector2 cameraWorldMin = Vector2.Transform(Vector2.Zero, Matrix.Invert(ViewMatrix));
Vector2 cameraSize = new Vector2(_viewport.Width, _viewport.Height) / _zoom;
Vector2 limitWorldMin = new Vector2(_limits.Value.Left, _limits.Value.Top);
Vector2 limitWorldMax = new Vector2(_limits.Value.Right, _limits.Value.Bottom);
Vector2 positionOffset = _position - cameraWorldMin;
_position = Vector2.Clamp(cameraWorldMin, limitWorldMin, limitWorldMax - cameraSize) + positionOffset;
}
}


Validating Position requires basically comparing the camera's position against the corners of our limiting range. Since the limiting range is defined in world space, I get the camera's corner position in world space by multiplying Vector2.Zero by the inverse of the View matrix.

Step 3)

Apply the validations to the setters:

public Vector2 Position
{
get { return _position; }
set
{
_position = value;
ValidatePosition();
}
}

public float Zoom
{
get { return _zoom; }
set
{
_zoom = value;
ValidateZoom();
ValidatePosition();
}
}

public Rectangle? Limits
{
set
{
_limits = value;
ValidateZoom();
ValidatePosition();
}
}

• Yeah, i read your blog, just couldn't think of a way to convert it to 3D, still can't unfortunately, though i just came up with my own method.
– Joel
Dec 23, 2011 at 1:51
• @DavidGouveia You're website looks to be an incredible resource for beginners. I like your code in this project as well. All I can say is keep it up :P nice works. Dec 23, 2011 at 4:29
• @MadPumpkin Thank you! I would like to update it more often, but at the moment there's this big project that's taking up most of my attention so it will have to wait. Dec 23, 2011 at 15:13

So after a bit of mucking around i sat down and took a different approach to the problem. Now I have it all working, and have put loads of comments in so hopefully anyone else with the problem will be able to use this as well. The only things that need to be changed to put into your own project, is: filedOfView, minZ, maxZ, minX, maxX and groundElevation.

float fieldOfView = 45; //field of view set for your projection (in degrees)

float minZ = -40; //minimum Z boundary
float maxZ = 85; //maximum Z boundary
float minX = -42; //minimum X boundary
float maxX = 85; //maximum X boundary

float groundElevation = 12; //elevation of boundaries. (The boundaries on Y:0 will have a totally different result to the boundaries on Y:30). In this case, this is the height of the LOWEST point of terrain that touches the edge.

float AR = GraphicsDevice.Viewport.AspectRatio; //Aspect ratio of screen

float perspectiveRatioY = (float)Math.Tanh(fieldOfView); //Ratio of camera height to distance along the Z axis
float perspectiveRatioX = perspectiveRatioY * AR; //Ratio of camera height to distance along the X axis

float maxZoomOut = Math.Min(((maxZ - minZ) / 2) / perspectiveRatioY, ((maxX - minX) / 2) / perspectiveRatioX) + groundElevation; //furthest distance possible to zoom out.

camPos.Y = Math.Min(maxZoomOut, camPos.Y); // adjust camera as to not go further out than possible

float camHeight = camPos.Y - groundElevation; //subtract ground elevation to basically allow us to deal with Y:30 and Y:0 in the same way.

float groundDis = perspectiveRatioY * camHeight; //distance shown from the middle of the screen to the top of the screen (on the height of Y:groundElevation)
camPos.Z = Math.Max(groundDis + minZ, Math.Min(camPos.Z, maxZ - groundDis)); //work out the min and max Z coordinates, and move the camPos.Z within (could be done with the clamp method).
groundDis = perspectiveRatioX * camHeight; //See 2 lines up, this works out for X though instead of Z
camPos.X = Math.Max(groundDis + minX, Math.Min(camPos.X, maxX - groundDis)); //See 2 lines up, this works out for X though instead of Z