Question

It's not that this doesn't make sense, but it just works out awkward 99% of the time.

Often in 2D graphics rectangles are initialized, stored and manipulated as a pair of points. In no particular language,

class Rect:
   p1, p2: point

It makes more sense to define a rectangle as two x values and two y values, like this:

class Rect
   xleft, xright: int
   ytop, ybottom: int

With two points, if at some place in the source code you want to make use of the y value of the top, you'd have to say rect.p1.y (hmmm, stop and think, is it p1 or p2) but with the four values as plain data members, it's clear and direct: rect.ytop (no thinking required!) The use of two points means that in dealing with the vertical, you have to tangle the horizontal; there's an extraneous relation between indepenent elements.

How did this two-point idea come about and why does it persists? Does it have some benefit over bare x and y coordinates?

ADDED NOTE: This question is in the context of X-Y aligned rectangles, such as in windows managers and GUI toolkits, not in the context of arbitrary shapes in drawing and painting app.

Was it helpful?

Solution

Have you considered that it is less error prone?

If you use (Point1, Point2) it is then very clear what you are specifying. If you provide 2 points, then the only possible error is that the user has mixed up their x and y when constructing the points as the order of the points doesn't matter.

If you supply 4 integers, then if someone isn't paying attention, they may supply (x1,x2,y1,y2) when you wanted (x1,y1,x2,y2) or vice versa. Also, some APIs such as WCF's Rect structure define a rectangle as (x, y, width, height) which could then cause confusion over what (1, 2, 3, 4) means. Is that (x, y, w, h) or (x1, y1, x2, y2) or (x1, x2, y1, y2)?

All in all, (Point1, Point2) seems a bit safer to me.

OTHER TIPS

I always liked defining a rectangle as a point + width and height, where the point is the upper-left corner of the rectangle.

class Rect {
  float x, y;
  float width, height;
}

And then add whatever methods you need to fetch the other metrics. Like the Java version

Actually, a rectagle isn't defined by 2 points. A rectangle can only be defined by two points if it is parallel to the axes.

There are several ways to represent rectangles that are parallel to the axes:

  1. Two diagonally opposite points
  2. One corner point, height and width
  3. Centre point, half height and width (uncommon, but sometimes useful).
  4. As two X coordinates and two Y coordinates

For (1), many libraries use a convention to determine which two points are used - topLeft and bottomRight, for example.

The choice of representation may be driven by the original purpose of the rectangle definition, but I imagine that it often arbitrary. The representations are equivalent in the information that they carry. They do, however, differ in the ease with which properties of the rectangle may be calculated and the the convenience with which operations can be performed on the reectangle.

Benefits of definition (1) over others include:

  • Consistency of API with other polygons, lines etc.
  • topLeft, bottomRight can be passed to any method that accepts points
  • Methods of Point class can be called on topLeft, bottomRight
  • Most properties can be derived easily, eg. bottomLeft, topRight, width, height, centre, diagonal length, etc.

Well p1: Point and p2: Point are each going to have two int coordinates in them anyway, so doesn't your class amount to the same thing?

And if you store those two points as first-class Point objects, don't you get a little more utility from them? In most graphical coordinate systems that I know of, points are subclassed in this way to create a hierarchy of objects: point -> circle -> ellipse and so on.

So if you make an object that doesn't use the Point class, you have divorced that object from the rest of the class hierarchy.

This is why I like Delphi's TRect. It's defined as a variant record (union struct in C-speak) that can be interpreted either as a TopLeft and a BottomRight point, or Top, Left, Bottom and Right integers, whichever is more convenient at the moment.

Surely if you define your rectangle as:

class Rect
{
    Point bottomLeft;
    Point topRight;
}

then you know straight away which point is which.

Even better would be to add extra properties that allowed you to manipulate the rectangle in which ever ways you needed for your application. These would simply update the underlying data structure.

By adding a transformation to the shape you can orient your rectangle any way you'd like. You'd still need an axis aligned bounding box for quick accept/reject checks :)

However, if your model allows rectangles in any orientation without applying a transformation then "bottom left" and "top right" have no meaning, which leads back to "p1" and "p2" (or something equivalent).

i think it makes more sense for a rectangle to be represented by an x and y extent and a point; you could even make the location point the center of the rectangle so it would be independent of rotation

but it was probably easiest to code it as two points!

I don't like it because we have thrown out a potential degree of freedom, which essentially allows for an arbitrary rotation. A general 2D rectangle has five unknowns (degrees of freedom). We could specify them as the coordinates of a point, the lengths of the two sides that form a vertex with this point, and the angle from the horizontal of the first line (the other being assumed to have an angle 90 degrees greater). An infinite number of other possibilities could also be used, but there are five independent quantities that must be specified. Some choices will lead to easier algebra than others, depending upon what is done with them.

Isnt that exactly the same thing as 2 points? How is this awkward... most drawing routines require points, not separate x/y components.

Defining rectangles as point pairs allows you to reuse the point as a vertex for another shape. Just a thought...

I believe it is mainly to establish uniformity between all shape primitives.

Sure you can define rectangle in many different ways, but how do you define a triangle, or a star, or a circle in a way that can use similar data structures?

All polygons can be defined by their points, with a short amount of logic to determine what to do with the points.

Graphics libraries primarily operate on these polygons in terms of vertices and edges, so points and the lines between them, all the calculations work on these two features, well that and facets, but that itself is just a function of the edges.

In two dimensions, storing a rectangle as two points is clearer than defining a particular corner and a width and height - consider negative width or height, or the calculations required to determine each option from the other.

Performing rotations on a rectangle defined by points is also much simpler than one defined with a point plus width and height.

I'd expect encapsulation to make this differentiation unimportant as a user of the class.

A rectangle should be defined as three points to be well defined in 3 dimensions. I'm not entirely sure of the requirement for defining a rectangle in 4 or more dimensions.

It's completely arbitrary. You need four pieces of information to draw a rectangle. The library designer(s) decided to represent it with two points (each with an x-y coordinate), but could easily have done it with x/y/w/h or top/bottom/left/right.

I suppose the OP's real question is: why was this particular choice made?

The choice of parameters are only important to the low-level designers / coders.

High-level users only need to think about:

  • IsPointInRect
  • Area
  • Intersection (or Clipping)
  • HasOverlap (same as Intersection.Area > 0)
  • Union (becomes a list of rectangles)
  • Subtraction (a list of rectangles that represent the same point set that is in rect A but not in rect B)
  • Transform
    • Shifts in X and Y
    • Rotation (0, 90, 180, 270)
    • Scaling in X and Y (see note)
  • Simple syntax for properties Xmin, Xmax, Ymin, Ymax, Width, Height so that the user does not need to know the exact choice of parameters.

Note: In order to minimize loss of precision during scaling transform, it is sometimes appropriate to implement a second Rect class that uses floating-point coordinates, so that intermediate results can be stored accurately in a sequence of transforms and only rounded to integer in the last step.

As @Steven says, I think it should be in terms of one (x,y) point, and a (w,h) size vector. That's because it's easy to fall into an ambiguity. Suppose you have the following filled-in rectangle starting at point (0,0).

  012
0 XXX
1 XXX
2 XXX

Clearly it's width,height are (3,3), but what is it's second point? Is it (2,2) or (3,3)?

This ambiguity can cause all kinds of problems.

I learned the hard way years ago that it's better to think of graphic coordinates as the lines between the pixels, not as the lines the pixels are on. That way there's no ambiguity.

Pa(x,y)*-----------------------------------*Pb(x,y)
       |                                   |
       |                                   |
       |                                   |
       |                                   |
       |                                   |
       |                                   |
Pc(x,y)*-----------------------------------*Pd(x,y)

We can define both Pb & Pc thus:

Pb(Pd(x),Pa(y))

and

Pc(Pa(x),Pd(y))

So there is no need to define all four points due to symmetry

Licensed under: CC-BY-SA with attribution
scroll top