Unity — Drawing Custom Debug Shapes — Part 6
Drawing Debug Rectangles
After covering circle and triangle in previous tutorials, we can continue with another geometric shape commonly used in game development, a rectangle. In this tutorial you will learn how to debug draw a rectangle in 3D space using various input parameters.
This tutorial is a part of Unity– Draw Custom Debug Shapes series. I would suggest to check the previous tutorials too, since they explain some of the basic geometry and Unity concepts in more detail.
Rectrangle 101
Rectangle is a quadrilateral (a four-sided polygon) defined by four vertices (A, B, C, D) connected by four sides (AB, BC, CD, DA) so that the opposite sides are equally long and parallel, with angles between any two sides being right (90°). Consequently, diagonals (AC, BD) have equal lengths and bisect each other. Intersection point of rectangle’s diagonals defines its center. These properties can be seen in the following image:
To draw such a rectangle in Unity engine using the Debug.DrawLine method, we can use one of two approaches:
- Drawing a rectangle based on its origin and extent (sides lengths),
- Drawing a rectangle based on two points.
Origin and Extent
To draw a rectangle we need to calculate points A, B, C, D based on the provided origin and extent. Origin is provided as position in 3D space, while the extent consists of lengths along the x and y axis. Extent x defines the length of sides AB and CD, while extent y defines the length of sides BC and DA.
Based on the image above, we can conclude that each point is distanced half of extent along each axis, in different directions, from the origin. Therefore we can define them as follows:
A = O-extent_x*0.5+extent_y*0.5
B = O-extent_x*0.5+extent_y*0.5
C = O-extent_x*0.5-extent_y*0.5
D = O+extent_x*0.5-extent_y*0.5
With this out of the way, we can continue in Unity. Our code will need to perform the following:
- Calculate points A, B, C and D
- Connect them using the Debug.DrawLine method to form a rectangle
Our method signature will need to contain the following parameters:
- position (origin),
- extent,
- color.
public static void DrawRectangle(Vector3 position, Vector2 extent, Color color)
Next, we need to calculate offsets along the x (right) and y (up) axes:
Vector3 rightOffset = Vector3.right * extent.x * 0.5f;
Vector3 upOffset = Vector3.up * extent.y * 0.5f;
The next step is to calculate the offsets of each points (although, we could have already calculate the position of each point, this will make sense a bit later down the line when we introduce orientation):
Vector3 offsetA = rightOffset + upOffset;
Vector3 offsetB = -rightOffset + upOffset;
Vector3 offsetC = -rightOffset - upOffset;
Vector3 offsetD = rightOffset - upOffset;
Finally, we can connect the points using the Debug.DrawLine and the given color:
DrawLine(position + offsetA, position + offsetB, color);
DrawLine(position + offsetB, position + offsetC, color);
DrawLine(position + offsetC, position + offsetD, color);
DrawLine(position + offsetD, position + offsetA, color);
Below you can find the full Debug.DrawRectangle method:
public static void DrawRectangle(Vector3 position, Vector2 extent, Color color)
{
Vector3 rightOffset = Vector3.right * extent.x * 0.5f;
Vector3 upOffset = Vector3.up * extent.y * 0.5f;
Vector3 offsetA = rightOffset + upOffset;
Vector3 offsetB = -rightOffset + upOffset;
Vector3 offsetC = -rightOffset - upOffset;
Vector3 offsetD = rightOffset - upOffset;
DrawLine(position + offsetA, position + offsetB, color);
DrawLine(position + offsetB, position + offsetC, color);
DrawLine(position + offsetC, position + offsetD, color);
DrawLine(position + offsetD, position + offsetA, color);
}
We can use this new method in our Example.cs class Update method:
void Update()
{
Debug.DrawRectangle(transform.position, new Vector2(2.0f, 1.0f), Color.magenta);
}
As we have done in the previous tutorials, we can also introduce the orientation. This part is explained in much more detail in the Draw Debug Circles.
To do so, we need to extend the DrawRectangle method signature:
public static void DrawRectangle(Vector3 position, Quaternion orientation, Vector2 extent, Color color)
Vector3 offsetA = orientation * (rightOffset + upOffset);
Vector3 offsetB = orientation * (-rightOffset + upOffset);
Vector3 offsetC = orientation * (-rightOffset - upOffset);
Vector3 offsetD = orientation * (rightOffset - upOffset);
Using this extended functionality in the Example.cs we get the result shown in the following image:
void Update()
{
Debug.DrawRectangle(transform.position, transform.rotation, new Vector2(2.0f, 1.0f), Color.magenta);
}
Two Points
An alternative way of defining/drawing a rectangle is by providing two diagonal points, as seen in the image below (for convenience I have named them P1 and P2, and also tagged each rectangle point with A, B, C and D respectively):
From the image above we can conclude the following:
- Since they are identical, it can be said that the point A is P1 units distanced from the origin,
- Extent along the x-axis is a distance between the P1 and P2 along that axis,
- Extent along the y-axis is a distance between the P1 and P2 along that axis,
- Point B is extentx units away from the point A, along the x-axis,
- Point C is extenty units away from the point B, along the y-axis,
- Point D is extenty units away from the point B, along the y-axis.
We can use that information to calculate each point of the rectangle:
A=O+P1
B=A+(extent_x,0)
C=B+(0,extent_y)
D=A+(0,extent_y)
Like with the previous method, we can already include the orientation parameter. To incorporate orientation we also need to rotate the x- and y-axis and add extent for each point along those rotated axes. This translates in C# code as follows:
public static void DrawRectangle(Vector2 point1, Vector2 point2, Vector3 origin, Quaternion orientation, Color color)
{
// Calculate extent as a distance between point1 and point2
float extentX = Mathf.Abs(point1.x - point2.x);
float extentY = Mathf.Abs(point1.y - point2.y);
// Calculate rotated axes
Vector3 rotatedRight = orientation * Vector3.right;
Vector3 rotatedUp = orientation * Vector3.up;
// Calculate each rectangle point
Vector3 pointA = origin + rotatedRight * point1.x + rotatedUp * point1.y;
Vector3 pointB = pointA + rotatedRight * extentX;
Vector3 pointC = pointB + rotatedUp * extentY;
Vector3 pointD = pointA + rotatedUp * extentY;
// Draw lines between the points
DrawLine(pointA, pointB, color);
DrawLine(pointB, pointC, color);
DrawLine(pointC, pointD, color);
DrawLine(pointD, pointA, color);
}
Using this new method in Update of our Example.cs class, we get the following:
void Update()
{
Vector2 pointA = new Vector2(-0.50f, -0.50f);
Vector2 pointB = new Vector2(2.0f, 1.0f);
Debug.DrawRectangle(pointA, pointB, transform.position, transform.rotation, Color.magenta);
}
DRY — Don’t Repeat Yourself
To improve the Debug.DrawRectangle methods, we can use DRY principle. DRY is a coding principle aimed at reducing repetition of code. Since both of our methods are drawing lines between four points to form a quadrilateral, we can extract that part of both methods into a new method called DrawQuad which takes these four points as parameters, as well as the drawing color:
public static void DrawQuad(Vector3 pointA, Vector3 pointB, Vector3 pointC, Vector3 pointD, Color color)
{
// Draw lines between the points
DrawLine(pointA, pointB, color);
DrawLine(pointB, pointC, color);
DrawLine(pointC, pointD, color);
DrawLine(pointD, pointA, color);
}
With that method in place, we can include it in our DrawRectangle methods as follows:
// Draw a rectangle defined by its position, orientation and extent
public static void DrawRectangle(Vector3 position, Quaternion orientation, Vector2 extent, Color color)
{
Vector3 rightOffset = Vector3.right * extent.x * 0.5f;
Vector3 upOffset = Vector3.up * extent.y * 0.5f;
Vector3 offsetA = orientation * (rightOffset + upOffset);
Vector3 offsetB = orientation * (-rightOffset + upOffset);
Vector3 offsetC = orientation * (-rightOffset - upOffset);
Vector3 offsetD = orientation * (rightOffset - upOffset);
DrawQuad(position + offsetA,
position + offsetB,
position + offsetC,
position + offsetD,
color);
}
// Draw a rectangle defined by two points, origin and orientation
public static void DrawRectangle(Vector2 point1, Vector2 point2, Vector3 origin, Quaternion orientation, Color color)
{
// Calculate extent as a distance between point1 and point2
float extentX = Mathf.Abs(point1.x - point2.x);
float extentY = Mathf.Abs(point1.y - point2.y);
// Calculate rotated axes
Vector3 rotatedRight = orientation * Vector3.right;
Vector3 rotatedUp = orientation * Vector3.up;
// Calculate each rectangle point
Vector3 pointA = origin + rotatedRight * point1.x + rotatedUp * point1.y;
Vector3 pointB = pointA + rotatedRight * extentX;
Vector3 pointC = pointB + rotatedUp * extentY;
Vector3 pointD = pointA + rotatedUp * extentY;
DrawQuad(pointA, pointB, pointC, pointD, color);
}
Please leave a comment if you have any questions or suggestions, or if you have some topic that you would like to to cover in the future.
In the meanwhile, you can also reach me on LinkedIn.
The following part covers some basic C# concepts to make the extension functions a bit more useful and convenient, but also how to draw Debug Spheres.