Joined: 8/23/2011(UTC) Posts: 245
Thanks: 6 times Was thanked: 12 time(s) in 11 post(s)
|
Ok, so this is actually a whole new camera class from what I had a month ago. I decided what I had then, though it worked, was done in far too complicated a way so just wrote it again. It's important to note that it still doesn't dovetail with DE's rendering very well in that, for example, you'd have to amend the DE Effect class to call into here to do its drawing so that any camera movement will be correctly respected. Also it's important to note that I have edited this code in this post because I use my own DrawManager class that incorporates RenderLayers. eg Where the code says Rect.DrawFilled(...) I have my own class and method Drawing.DrawFilledRectangle(...) - and I may have made a mistake. Just bear in mind these lines of code are untested! Code:using Delta.ContentSystem.Rendering;
using Delta.Engine;
using Delta.Rendering.Basics.Drawing;
using Delta.Rendering.Basics.Materials;
using Delta.Utilities.Datatypes;
using Delta.Utilities.Datatypes.Advanced;
using System;
namespace X
{
internal static class Camera
{
// Used to help determine if the user has attempted to move the camera outside of the world
private static float WorldAspectRatio { get; set; }
// LetterBox is the viewport onto the world. It represents the section of the screen that is drawn on.
// As such it can be smaller than ScreenSpace.DrawArea (but should not go outside of it)
// eg. if ScreenSpace.Draw area is a Rectangle from (.1,.1)->(.9,.9) and LetterBox is (.1,.1)->(.5,.9) then
// the camera does not draw on the right hand side from (.5,.1) to (.9,.9).
private static Rectangle LetterBox { get; set; }
// If LetterBox is not the same size as ScreenSpace.DrawArea then these four Rectangles represent the sections of screen not drawn on
// This if for drawing a coloured backdrop for UI elements etc.
private static Rectangle FrameTop { get; set; }
private static Rectangle FrameLeft { get; set; }
private static Rectangle FrameRight { get; set; }
private static Rectangle FrameBottom { get; set; }
// This variable records the amount the camera has panned
private static Point Offset { get; set; }
// This variable represents the amount of zoom that has been applied
private static float Zoom { get; set; }
// This is how much zoom occurs per 'tick' of zoom - eg per tick of mousewheel
private const float ZoomIncrement = .975f;
#region Init
/// <summary>
/// This should always be called prior to using the camera or on any aspect change
/// </summary>
internal static void Init(float worldAspectRatio, Rectangle letterBox)
{
WorldAspectRatio = worldAspectRatio;
LetterBox = letterBox;
var drawArea = ScreenSpace.DrawArea;
FrameTop = new Rectangle(drawArea.TopLeft, new Size(drawArea.Width, LetterBox.Top - drawArea.Top));
FrameLeft = new Rectangle(new Point(drawArea.Left, LetterBox.Top), new Size(LetterBox.Left - drawArea.Left, LetterBox.Height));
FrameRight = new Rectangle(LetterBox.TopRight, new Size(drawArea.Right - LetterBox.Right, LetterBox.Height));
FrameBottom = new Rectangle(new Point(drawArea.Left, LetterBox.Bottom), new Size(drawArea.Width, drawArea.Bottom - LetterBox.Bottom));
Offset = Point.Zero;
Zoom = 1;
}
/// <summary>
/// This draws the frame around the letterbox
/// </summary>
internal static void DrawFrame(Color color)
{
Rect.DrawFilled(FrameTop, color);
Rect.DrawFilled(FrameLeft, color);
Rect.DrawFilled(FrameRight, color);
Rect.DrawFilled(FrameBottom, color);
}
#endregion
#region Transformations
/// <summary>
/// Translates from quadratic screen coordinates (0,0)-(1,1) to world coordinates - taking into account the camera's zoom & movement
/// This might be used for, for example, finding the world coordinates of where the user clicked the mouse, in order to select an object
/// </summary>
internal static Point ScreenToWorld(Point point)
{
point /= Zoom;
point += Offset;
return point;
}
/// <summary>
/// Translates from world coordinates to screen coordinates
/// This will be used for, for example, drawing objects
/// </summary>
internal static Point WorldToScreen(Point point)
{
point -= Offset;
point *= Zoom;
return point;
}
#endregion
#region Draw Primitives
/// <summary>
/// Clears the letterbox area of the screen, setting it to the required colour
/// </summary>
internal static void Clear(Color color)
{
Rect.DrawFilled(LetterBox, color);
}
/// <summary>
/// Draws a filled rectangle at the required position, in the required colour
/// </summary>
internal static void DrawFilledRectangle(Rectangle worldDrawArea, Color color)
{
var drawArea = Rectangle.FromCorners(WorldToScreen(worldDrawArea.TopLeft), WorldToScreen(worldDrawArea.BottomRight));
if (LetterBox.Contains(drawArea) == ContainmentType.None) return;
Rect.DrawFilled(drawArea, color);
}
/// <summary>
/// Draws an unfilled rectangle at the required position, in the required colour
/// </summary>
internal static void DrawRectangleOutline(Rectangle worldDrawArea, Color color)
{
var drawArea = Rectangle.FromCorners(WorldToScreen(worldDrawArea.TopLeft), WorldToScreen(worldDrawArea.BottomRight));
if (LetterBox.Contains(drawArea) == ContainmentType.None) return;
Rect.DrawFilled(drawArea, color, drawLayer);
}
/// <summary>
/// Draws a filled circle at the required position, in the required colour
/// </summary>
internal static void DrawFilledCircle(Point center, float radius, Color color)
{
center = WorldToScreen(center);
radius *= Zoom;
if (center.X + radius < LetterBox.Left || center.X - radius > LetterBox.Right || center.Y - radius > LetterBox.Bottom || center.Y + radius < LetterBox.Top) return;
Circle.DrawFilled(center, radius, color);
}
/// <summary>
/// Draws an unfilled circle at the required position, in the required colour
/// </summary>
internal static void DrawCircleOutline(Point center, float radius, Color color)
{
center = WorldToScreen(center);
radius *= Zoom;
if (center.X + radius < LetterBox.Left || center.X - radius > LetterBox.Right || center.Y - radius > LetterBox.Bottom || center.Y + radius < LetterBox.Top) return;
Circle.DrawOutline(center, radius, color);
}
/// <summary>
/// Draws a line at the required position, in the required colour
/// </summary>
internal static void DrawLine(Point point1, Point point2, Color color)
{
point1 = WorldToScreen(point1);
point2 = WorldToScreen(point2);
var left = Math.Min(point1.X, point2.X);
var right = Math.Max(point1.X, point2.X);
var top = Math.Min(point1.Y, point2.Y);
var bottom = Math.Max(point1.Y, point2.Y);
if (right < LetterBox.Left || left > LetterBox.Right || top > LetterBox.Bottom || bottom < LetterBox.Top) return;
Line.Draw(point1, point2, color);
}
#endregion
#region Draw Material2D
/// <summary>
/// Draws an image at the required position, with the required rotation
/// </summary>
internal static void DrawImage(Material2D image, Rectangle worldDrawArea, float rotation = 0)
{
var drawArea = Rectangle.FromCorners(WorldToScreen(worldDrawArea.TopLeft), WorldToScreen(worldDrawArea.BottomRight));
if (LetterBox.Contains(drawArea) == ContainmentType.None) return;
image.Draw(drawArea, rotation);
}
#endregion
#region Draw Material2DColored
/// <summary>
/// Draws an image but clips the UV coordinates to the letterbox
/// This should only be used for a backdrop image that fills the whole worldarea (else fix the uv coordinate calculation)
/// </summary>
internal static void DrawImageClipped(Material2DColored image, Rectangle worldDrawArea)
{
var drawArea = Rectangle.FromCorners(WorldToScreen(worldDrawArea.TopLeft), WorldToScreen(worldDrawArea.BottomRight));
if (LetterBox.Contains(drawArea) == ContainmentType.None) return;
float left = (LetterBox.Left - drawArea.Left) / drawArea.Width;
float top = (LetterBox.Top - drawArea.Top) / drawArea.Height;
float right = (LetterBox.Right - drawArea.Left) / drawArea.Width;
float bottom = (LetterBox.Bottom - drawArea.Top) / drawArea.Height;
left = image.UV.Left + image.UV.Width * left;
top = image.UV.Top + image.UV.Height * top;
right = image.UV.Left + image.UV.Width * right;
bottom = image.UV.Top + image.UV.Height * bottom;
image.Draw(LetterBox, new Rectangle(left, top, right - left, bottom - top));
}
/// <summary>
/// Draws an image at the required position, with the required rotation, with the required alpha
/// </summary>
internal static void DrawImage(Material2DColored image, Rectangle worldDrawArea, float rotation = 0, float alpha = 1)
{
var drawArea = Rectangle.FromCorners(WorldToScreen(worldDrawArea.TopLeft), WorldToScreen(worldDrawArea.BottomRight));
if (LetterBox.Contains(drawArea) == ContainmentType.None) return;
image.BlendColor.A = alpha;
image.Draw(drawArea, rotation);
}
/// <summary>
/// Draws an image at the required position, with the required rotation, applying a blendcolor
/// </summary>
internal static void DrawImage(Material2DColored image, Rectangle worldDrawArea, Color blendColor, float rotation = 0)
{
var drawArea = Rectangle.FromCorners(WorldToScreen(worldDrawArea.TopLeft), WorldToScreen(worldDrawArea.BottomRight));
if (LetterBox.Contains(drawArea) == ContainmentType.None) return;
image.BlendColor = blendColor;
image.Draw(drawArea, rotation);
}
#endregion
#region Movement
/// <summary>
/// Zoom the camera viewpoint into or out of the world (eg responding to a mousewheel or finger pinch)
/// FixViewpoint will attempt to pan the camera to keep the viewpoint inside the worldarea - and if it can't it will rollback the zoom
/// </summary>
/// <param name="ticks"></param>
internal static void ApplyZoom(float ticks)
{
float validZoom = Zoom;
Point validOffset = Offset;
// When you zoom, you wish the center of the screen to remain pointed at the same spot in the world.
// Hence a zoom also adjusts Offset
Point center = ScreenToWorld(LetterBox.Center);
Zoom *= (float)Math.Pow(ZoomIncrement, -ticks);
Offset = center - LetterBox.Center / Zoom;
//TODO: Instead, find closest valid zoom
if (!FixViewpoint()) { Zoom = validZoom; Offset = validOffset; }
}
/// <summary>
/// Pans the camera around the world (eg responding to a mousedrag or fingerdrag)
/// FixViewpoint will re-pan the camera if this pan took the viewpoint outside of the world
/// </summary>
internal static void ApplyPan(Point point)
{
Point validOffset = Offset;
Offset -= point / Zoom;
//TODO: Instead, find closest valid translate
if (!FixViewpoint()) Offset = validOffset;
}
/// <summary>
/// Checks the viewpoint has not brought anything outside of the world inside the letterbox, and if so attempts to pan to shift it back out of view
/// If it cannot find a way to pan it, it returns false
/// </summary>
/// <returns></returns>
private static bool FixViewpoint()
{
var tl = ScreenToWorld(LetterBox.TopLeft);
var offsetX1 = tl.X;
if (offsetX1 > 0) offsetX1 = 0;
var tr = ScreenToWorld(LetterBox.TopRight);
var offsetX2 = tr.X - WorldAspectRatio - offsetX1;
if (offsetX2 < 0) offsetX2 = 0;
if (offsetX1 != 0 && offsetX2 != 0) return false;
var offsetY1 = tl.Y;
if (offsetY1 > 0) offsetY1 = 0;
var bl = ScreenToWorld(LetterBox.BottomLeft);
var offsetY2 = bl.Y - 1 - offsetY1;
if (offsetY2 < 0) offsetY2 = 0;
if (offsetY1 != 0 && offsetY2 != 0) return false;
Offset -= new Point(offsetX1 + offsetX2, offsetY1 + offsetY2);
return true;
}
#endregion
}
}
Edited by user Wednesday, May 9, 2012 9:15:32 AM(UTC)
| Reason: Not specified
|