Title: Find rectangles defined by a side and aspect ratio in C#
You probably haven't needed to make your program draw rectangles that are defined by specifying one of its sides. I haven't either, but I want this for another program that I plan to write.
Of course a single side isn't quite enough to completely determine a rectangle. To use this example, you also need to specify the rectangle's desired aspect ratio. (The width / height ratio.)
This program performs several tasks such as letting you draw a line segment and reading the aspect ratio either as a floating point value or in a width:height format. Those details aren't terribly interesting, however, so I won't cover them here. Download the example to see then in all their glory. Here I'm just going to explain how to find rectangles from the edge segment and aspect ratio.
There are two main tasks that the program must perform to find rectangles defined by an edge and an aspect ratio. First, it must find edges that make right angles with the original segment. Second, it must make those edges the correct lengths to give the desired aspect ratio.
Perpendicular Vectors
I've covered this before (and probably will again), but it's worth repeating. If you have a vector <vx, vy>, then the vectors <vx, -vy> and <-vx, vy> are perpendicular to, and have the same length as, the original vector.
In the picture on the right, the red vector is the original. Note that I've chosen a vector where vx and vy are both positive to reduce confusion. (Remember that Y coordinates increase downward.) This all works if vx or vy is negative, but it's a bit less intuitive.
If you look at the picture, you can see how the blue and green vectors are defined. Note that the blue vector <vx, -vy> points to the right and the green vector <-vx, vy> points to the left as you look down the length of the original red vector.
Setting Edge Lengths
After we find a vector perpendicular to the given edge, we need to figure out how far to go in that direction to define the two adjacent sides. We can use the aspect ratio to do that.
Suppose the given edge has length M and let N represent the length of the adjacent edges. Then we know that either M / N = aspect_ratio or N / M = aspect_ratio, depending on whether you consider the given edge as along the rectangle's length or width. We can solve those equations for N to give two possible values for the length of the adjacent edges.
N1 = M / aspect_ratio
N2 = M * aspect_ratio
Now suppose the given edge has vertices p1 and p2, and the rectangle's remaining vertices are p3 and p4. We can use the perpendicular vectors to find points p3 and p4.
For example, consider the first solution for N1. If you take a vector of length M and divide its X and Y components by the aspect ratio, you get a new vector pointing in the same direction but with length N1 as desired. Similarly you can multiply the vector's components by the aspect ratio to get a vector pointing in the same direction but with length N2.
Now add the scaled vector's components to the coordinates of point p1 to get point p4 as shown in the picture on the right. Similarly add the scaled vector to point p2 to get point p3. That gives you the four points that define one of the rectangles.
Repeat this process for the two possible values for N1 and N2 and for the two perpendicular directions <vx, -vy> and <-vx, vy> to get a total of four rectangles defined by the original side and the aspect ratio.
FindRectangles
The following FindRectangles method uses the technique described in the preceding section to find rectangles defined by a side and aspect ratio.
private List<PointF[]> FindRectangles(float aspect_ratio,
PointF p1, PointF p2)
{
// Get the vector p1 --> p2.
float vx = p2.X - p1.X;
float vy = p2.Y - p1.Y;
if (Math.Sqrt(vx * vx + vy * vy) < 0.1) return null;
PointF p3, p4;
List<PointF[]> result = new List<PointF[]>();
float perp_x, perp_y;
// Make rectangle 1.
perp_x = vy * aspect_ratio;
perp_y = -vx * aspect_ratio;
p3 = new PointF(p2.X + perp_x, p2.Y + perp_y);
p4 = new PointF(p1.X + perp_x, p1.Y + perp_y);
result.Add(new PointF[] { p1, p2, p3, p4 });
// Make rectangle 2.
p3 = new PointF(p2.X - perp_x, p2.Y - perp_y);
p4 = new PointF(p1.X - perp_x, p1.Y - perp_y);
result.Add(new PointF[] { p1, p2, p3, p4 });
// Make rectangle 3.
perp_x = vy / aspect_ratio;
perp_y = -vx / aspect_ratio;
p3 = new PointF(p2.X + perp_x, p2.Y + perp_y);
p4 = new PointF(p1.X + perp_x, p1.Y + perp_y);
result.Add(new PointF[] { p1, p2, p3, p4 });
// Make rectangle 4.
p3 = new PointF(p2.X - perp_x, p2.Y - perp_y);
p4 = new PointF(p1.X - perp_x, p1.Y - perp_y);
result.Add(new PointF[] { p1, p2, p3, p4 });
return result;
}
The method takes as parameters the aspect ratio and two points that define the initial edge. It returns a list of arrays of points. Each array of points contains the four points that define a rectangle.
The code starts by finding the vector between the two known points.
To make the first rectangle, it switches the vx and vy components to get a perpendicular vector and multiples the components by the aspect ratio to get a vector with the necessary length N2. It then adds the vector's components to the initial points p1 and p2 to find the rectangle's remaining two points p3 and p4.
The method adds the points to an array and adds the array to the result list.
The code then repeats those steps to generate the other three rectangles and add them to the result list. When it has defined all three rectangles, the method returns the list.
Drawing the Rectangles
The last part of the example that I'm going to describe is the code that draws the rectangles. The program uses the following code to store various drawing values.
private PointF Point1, Point2;
private List<PointF[]> Rectangles = new List();
private Brush[] RectBrushes =
{
new SolidBrush(Color.FromArgb(128, Color.Red)),
new SolidBrush(Color.FromArgb(128, Color.Red)),
new SolidBrush(Color.FromArgb(128, Color.Blue)),
new SolidBrush(Color.FromArgb(128, Color.Blue)),
};
private Pen[] RectPens =
{
Pens.Red,
Pens.Red,
Pens.Blue,
Pens.Blue,
};
The values Point1 and Point2 are the endpoints of the initial edge that you draw. The program uses the FindRectangles method to store the found rectangles in the Rectangles list. The RectBrushes and RectPens arrays hold the brushes and pens that the program uses to draw the rectangles.
The following Paint event handler does the drawing.
private void picCanvas_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(picCanvas.BackColor);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int i = 0; i < Rectangles.Count; i++)
{
e.Graphics.FillPolygon(RectBrushes[i], Rectangles[i]);
e.Graphics.DrawPolygon(RectPens[i], Rectangles[i]);
}
if (Point1 != Point2)
{
e.Graphics.DrawLine(Pens.Black, Point1, Point2);
}
}
After some setup, the code loops through the rectangles in the Rectangles list, filling and then outlining each. If you look at the RectBrushes array, you'll see that the program first draws two translucent red rectangles and then draws two translucent blue rectangles. if you look at the picture at the top of this post, you'll see that the two first rectangles come out purple (blue on top of red). The third and fourth rectangles are blue and they cover the first two rectangles.
Download the example to experiment with it and to see additional details.
|