-2

I'm struggling with a problem I thought should be easy to solve:

I'm given two points x1, x2 and a width value. How can I calculate two other points parallel to x1 and x2 so that it forms a rectangle?

I tried answers from here 1 and here 2. Though both solutions are off.

As background: This is about projecting an image into real world coordinates. Therefore I need to find the parallel line to the line I'm provided with, so that the points of both lines create a rectangle. I do not want to apply a rotation on my own.

Here is a drawing that shows what I want to achieve: enter image description here

In the example you see x1, x2 and the width I'm provided with. And I'm looking for x3 and x4 so that the points form a rectangle.

I'm looking for a C++ implementation if possible.

1 https://gamedev.stackexchange.com/questions/86755/how-to-calculate-corner-positions-marks-of-a-rotated-tilted-rectangle

2 Calculating vertices of a rotated rectangle

Here is the code I've implemented. As you can see I'm using top right and top left coordinates that I'm provided with. But I'd rather find a line parallel to the provided points instead:

   double distance = 77.5;//[self normalizedDistanceWithCRS:crs p1:topLeft p2:topRight];

    // calculate the rotated coordinates for bottom right and bottom left with provided height
    double angle = atan2(sinuTL.y - sinuTR.y, sinuTL.x - sinuTR.x); //  * 180 / M_PI

    double x = distance;
    double y = height;
    double xBRTrans = x*cos(angle) - y*sin(angle);
    xBRTrans = sinuTL.x - xBRTrans;
    double yBRTrans = x*sin(angle) + y*cos(angle);
    yBRTrans = sinuTL.y - yBRTrans;

    x = 0;
    y = height;
    double xBLTrans = x*cos(angle) - y*sin(angle);
    xBLTrans += sinuTL.x;
    double yBLTrans = x*sin(angle) + y*cos(angle);
    yBLTrans = sinuTL.y - yBLTrans;

** Update **

I've adapted the code from the solution provided below, The result is still not what I expect. The two points on the left are given, the two points on the right are calculated. You can see that there is an offset (the points should be at the corner of the building. Also ignore the blue point in the middle - it's meaningless to this question):

enter image description here

The code:

double height = 57;
// get coords from provided input
double x1x=629434.24373957072, x1y=5476196.7595944777, x2x=629443.08914538298, x2y=5476120.1852802411;
// x2x3 = Vector from point x2 to point x3, assume x value as 1
double x2x3x = 1;
// calculate y-value, using the fact that dot-product of orthogonal vectors is 0
double x2x3y = x2x*x2x3x / (-1 * x2y);
// calculate length of vector x2x3
double length_e_vec_x2_x3 = sqrt(pow(x2x3x,2) + pow(x2x3y,2));
// stretch vector to provided witdh
x2x3x = x2x3x*height / length_e_vec_x2_x3;
x2x3y = x2x3y*height / length_e_vec_x2_x3;
// since x2x3 and x1x4 are equal, simple addition remains
double x3x, x3y, x4x, x4y;
x3x = x2x + x2x3x;
x3y = x2y + x2x3y;
x4x = x1x + x2x3x;
x4y = x1y + x2x3y;

UPDATE

Actually, all answers and also my own code work as expected. I was unfortunately blindfolded and didn't notice the issue was due to an inappropriate geo projection used for this area. So the coordinates come from WGS84 long/lat, and before the calculation is done get converted into a sinusoidal projection and later back into WGS84. The sinusoidal projection preserves the area (equal area projection) - but distorts shapes within an area. And you cannot just add some meters, and later convert back. I should have realized this earlier and was looking at the wrong place.

I'll choose the most elaborate answer as "winner". Though after testing I can say that all provided solutions actually work.

benjist
  • 2,740
  • 3
  • 31
  • 58
  • 2
    You have been in SO long enough to know that you need to show what you have tried and where you are stuck. – R Sahu Mar 28 '18 at 20:24
  • I thought I was clear about that: I tried to implement both mentioned solutions, and I'm stuck because the point are somehwat off. Say 3 meters out of 50 meters I mentioned. Likely because of the rotation these methods apply. I'm stuck here because I want to find mentioned parallel segment so that the found line segment forms a rectangle. – benjist Mar 28 '18 at 20:28
  • Why did you also fail to mention that you are provided with `y1` and `y2`? That's a vital piece of information otherwise, what we have here is simply a "draw a rectangle given height and width" problem – smac89 Mar 28 '18 at 21:50
  • @smac89 I think that should be clear by the second sentence: "I'm given two POINTS x1, x2". Though, I could name them p1, p2. – benjist Mar 28 '18 at 21:55
  • Ahh yea, I missed that. Sorry – smac89 Mar 28 '18 at 21:59
  • The code I provided is not working outside of a relative coordinate system with X1 as origin. Since the dot product of the vector X1X2 has to be orthogonal with X2X3, Now if X1 is 0, X1X2 is equal to X2. I updated the parts depending on a relative origin, and added a seperate example using a relative origin. – Y.S Mar 29 '18 at 09:37

2 Answers2

1

General recommonendation:

If I were you I would build classes for the vectors, and build functions for the required operations, however this example should do what you wish.

Vectors, absolute and relative coordinates:

Important note: you are working with coordinates, and this is a really simplified approach to it. If the person providing you a solution is setting a given Point to 0/0, aka the Origin, you can't just Change this. I changed the code below to adjust to the changes you did to the Input provided.

double width = 35;

// get coords from provided input
double x1x=0, x1y=0, x2x=x, x2y=y;
// x2x3 = Vector from point x2 to point x3, assume x value as 1
double x2x3x = 1;
// calculate y-value, using the fact that dot-product of orthogonal vectors is 0
double x2x3y = x2x*x2x3x / (-1 * x2y);
// calculate length of vector x2x3
double length_e_vec_x2_x3 = sqrt(pow(x2x3x,2) + pow(x2x3y,2));
// stretch vector to provided witdh
x2x3x = x2x3x*width / length_e_vec_x2_x3;
x2x3y = x2x3y*width / length_e_vec_x2_x3;
// since x2x3 and x1x4 are equal, simple addition remains
double x3x, x3y, x4x, x4y;
x3x = x2x + x2x3x;
x3y = x2y + x2x3y;
x4x = x1x + x2x3x;
x4y = x1y + x2x3y;

// check results
cout << "X1: " << x1x << "/" << x1y << endl;
cout << "X2: " << x2x << "/" << x2y << endl;
cout << "X3: " << x3x << "/" << x3y << endl;
cout << "X4: " << x4x << "/" << x4y << endl;

Output:

X1: 629434/5.4762e+06
X2: 629443/5.47612e+06
X3: 629500/5.47613e+06
X4: 629491/5.4762e+06

Verification

As mentioned in the comments of this code, the dot-product of two vectors will return 0 if those vectors are orthogonal to each other. By using this, one can verify the provided results. Add this little amount of code to verify the results:

// verify results
cout << "Dotproduct should be 0: " << (x2x*x2x3x)+(x2y*x2x3y) << endl;

Output of verification

Dotproduct should be 0: 5.68434e-14

Which prints 0, so the code is doing what it should do.

Improvements

However since you use rather big Numbers, using a float instead of a double might help. Also converting x1 into the origin of your little system might improve it. Finally a more suitable datastructure would be appreciated.

// using x1 as origin:
double x1x0 = 0, x1y0 = 0, x2x0 = x2x - x1x, x2y0 = x2y - x1y;

double x2x3x0 = 1;
// calculate y-value, using the fact that dot-product of orthogonal vectors is 0
double x2x3y0 = x2x0*x2x3x0 / (-1 * x2y0);
// calculate length of vector x2x3
double length_e_vec_x2_x30 = sqrt(pow(x2x3x0, 2) + pow(x2x3y0, 2));
// stretch vector to provided witdh
x2x3x0 = x2x3x0*width / length_e_vec_x2_x30;
x2x3y0 = x2x3y0*width / length_e_vec_x2_x30;
// since x2x3 and x1x4 are equal, simple addition remains
double x3x0, x3y0, x4x0, x4y0;
x3x0 = x2x0 + x2x3x0;
x3y0 = x2y0 + x2x3y0;
x4x0 = x1x0 + x2x3x0;
x4y0 = x1y0 + x2x3y0;

// check results
cout << "X1: " << x1x0 << "/" << x1y0 << endl;
cout << "X2: " << x2x0 << "/" << x2y0 << endl;
cout << "X3: " << x3x0 << "/" << x3y0 << endl;
cout << "X4: " << x4x0 << "/" << x4y0 << endl;

// verify results
cout << "Dotproduct should be 0: " << (x2x0*x2x3x0) + (x2y0*x2x3y0) << endl;

// compare results (adding offset before comparing):
cout << "X3 to X30: " << x3x0+x1x-x3x << "/" << x3y0+x1y-x3y << endl;
cout << "X4 to X40: " << x4x0 +x1x-x4x << "/" << x4y0 +x1y-x4y << endl;

Results:

X1: 0/0
X2: 8.84541/-76.5743
X3: 65.4689/-70.0335
X4: 56.6235/6.5408
Dotproduct should be 0: 5.68434e-14
X3 to X30: 0/0
X4 to X40: 0/0

Now the output using floats:

X1: 629434/5.4762e+06
X2: 629443/5.47612e+06
X3: 629500/5.47613e+06
X4: 629491/5.4762e+06
Dotproduct should be 0: 0
X1: 0/0
X2: 8.8125/-77
X3: 65.4428/-70.5188
X4: 56.6303/6.48123
Dotproduct should be 0: 0
X3 to X30: 0/0
X4 to X40: 0/0

Building the whole thing less messy:

using namespace std;

class vector2D
{
protected:
    bool equal(vector2D& param) { return this->X == param.X && this->Y == param.Y; }
    vector2D absAlVal() { return vector2D(abs(X), abs(Y)); }

public:
    float X, Y;

    vector2D(float x, float y) : X(x), Y(y) {};
    vector2D() : X(0), Y(0) {};

    vector2D operator+ (vector2D& param) { return vector2D(this->X+param.X,this->Y+param.Y); }
    vector2D operator- (vector2D& param) { return vector2D(this->X - param.X, this->Y - param.Y); }
    bool operator!=(vector2D& param) { return this->equal(param); }


    vector2D getUnitVector()
    {
        return vector2D(this->X / this->getLength(), this->Y / this->getLength());
    }
    bool parallel(vector2D& param) { return (this->getUnitVector()).equal(param.getUnitVector()); }
    bool colinear(vector2D& param) { return (this->getUnitVector().absAlVal()).equal(param.getUnitVector().absAlVal()); }

    float dotproduct(vector2D vec)
    {
        return this->X * vec.X + this->Y * vec.Y;
    }
    vector2D dotproduct(float scalar)
    {
        return vector2D(this->X * scalar, this->Y * scalar);
    }
    float getLength(void)
    {
        return sqrt(pow(this->X, 2) + pow(this->Y, 2));
    }

};

void main()
{
    // get coords from provided input
    float x1x = 629434.24373957072, x1y = 5476196.7595944777, x2x = 629443.08914538298, x2y = 5476120.1852802411;
    float width = 35;

    // Build vectors
    vector2D X1 = vector2D(x1x, x1y), X2 = vector2D(x2x, x2y), X3, X4, X2X3, X1X2=X2-X1;
    // assum x-direction for X2X3 is positive, chosing 1
    X2X3.X = 1;
    // calculate y-direction using dot-product
    X2X3.Y = X1X2.X*X2X3.X / (-1 * X1X2.Y);
    //check if assumtion is correct:
    cout << "Evaluate wether vector has been build accordingly or not:" << endl;
    cout << "Dotproduct of X1X2 * X2X3 should be 0 -> Result:" << X1X2.dotproduct(X2X3) << endl;
    // stretch X2X3 to width
    X2X3=X2X3.getUnitVector().dotproduct(width);

    // Create X3 and X4 by simple addition:
    X3 = X2 + X2X3;
    X4 = X1 + X2X3;

    // print Points:
    cout << "Summary of Points X / Y coordinates:" << endl;
    cout << "X1: " << X1.X << "/" << X1.Y << endl;
    cout << "X2: " << X2.X << "/" << X2.Y << endl;
    cout << "X3: " << X3.X << "/" << X3.Y << endl;
    cout << "X4: " << X4.X << "/" << X4.Y << endl;
    // compare sides
    cout << "\n" << "Lenght of sides:" << endl;
    cout << "X1X2: " << (X2 - X1).getLength() << " -> should be same length as X3X4" << endl;
    cout << "X2X3: " << (X3 - X2).getLength() << " -> should be same length as X4X1 and with, which is:" << width << endl;
    cout << "X3X4: " << (X4 - X3).getLength() << " -> should be same length as X1X2" << endl;
    cout << "X4X1: " << (X1 - X4).getLength() << " -> should be same length as X2X3, which is:" << width << endl;
}
Y.S
  • 311
  • 1
  • 10
  • Thanks. Please see my updated question. The issue still exists. I've added actual values used in my program. – benjist Mar 28 '18 at 21:35
1

Given a vector (x, y), the direction (y, -x) is rotated by 90 degrees clockwise with respect to it. This is exactly the rotation we need to perform to obtain the direction of the side x1 -> x4 from x1 -> x2.

// input point struct
struct point { double x, y; };

// pass in output points by reference
void calculate_other_points(
   const point& x1, const point& x2, // input points x1 x2
   double w,                         // input width
   point& x3, point& x4)             // output points x3 x4
{
   // span vector x1 -> x2
   double dx = x2.x - x1.x,
          dy = x2.y - x1.y;
   // height
   double h = hypot(dx, dy);

   // perpendicular edge x1 -> x4 or x2 -> x3
   double px =  dy * (w / h),
          py = -dx * (w / h);

   // add onto x1 / x2 to obtain x3 / x4
   x4.x = x1.x + px; x4.y = x1.y + py;
   x3.x = x2.x + px; x3.y = x2.y + py;
}

Note that my code is similar in principle to that of the previous answer, but is somewhat more optimized, and (hopefully) fixes the direction issue.

meowgoesthedog
  • 14,670
  • 4
  • 27
  • 40
  • 1
    I think `hypot()` is to be preferred over `sqrt()`, see here http://coliru.stacked-crooked.com/a/0c36801163209d24 and https://stackoverflow.com/a/32436148/2836621 – Mark Setchell Mar 29 '18 at 08:50
  • @MarkSetchell thanks for the advice; although I don't know how useful this is for OP's purposes given that it is a simple graphical application and doubles are being used – meowgoesthedog Mar 29 '18 at 11:16