Extending a line between 2 points in 3D space - c

Let's say I have 2 points in 3D space, one at:
x=2, y=3, z=5
and the second one at:
x=6, y=7, z=10
What is the fastest way, in code, to calculate the coordinates of a third point from extending (for example, doubling) the distance between those two points (relative to point one)?

If you want a point extended as far beyond (x2,y2,z2) as that is beyond (x1,y1,z1):
x3 = x2 + (x2 - x1) (= 10)
y3 = y2 + (y2 - y1) (= 11)
z3 = z2 + (z2 - z1) (= 15)
or:
(x2 * 2 - x1, y2 * 2 -y1, z2 * 2 - z1)
Simple as that.
If you want something other than double the length, you can scale the (x2 - x1)-type terms. For example, if you want it 50% longer than the current line, multiply them by 0.5 (+50%). If you want it three times longer, multiply them by two (+200%).
In terms of code that can perform this extension, something like this, which gives you an endpoint pDest that, along with, p1 forms a line percent times the size of p1-p2:
typedef struct {
double x;
double y;
double z;
} tPoint3d;
void extend (tPoint3d *p1, tPoint3d *p2, double percent, tPoint3d *pDest) {
percent -= 100.0; // what to ADD
percent /= 100.0; // make multiplier
pDest->x = p2->x + percent * (p2->x - p1->x); // scale each point
pDest->y = p2->y + percent * (p2->y - p1->y);
pDest->z = p2->z + percent * (p2->z - p1->z);
}

Related

How does glRotatef rotate around local indices?

I am replacing my project's use of glRotatef because I need to be able to transform double matrices. glRotated is not an option because OpenGL does not guarantee the stored matrices or any operations performed to be double precision. However, my new implementation only rotates around the global axes, and does not give the same result as glRotatef.
I have looked at some implementations of glRotatef (like OpenGl rotate custom implementation) and don't see how they account for the initial transformation matrix's local axes when calculating the rotation matrix.
I have a generic rotate function, taken (with some changes) from https://community.khronos.org/t/implementing-rotation-function-like-glrotate/68603:
typedef double double_matrix_t[16];
void rotate_double_matrix(const double_matrix_t in, double angle, double x, double y, double z,
double_matrix_t out)
{
double sinAngle, cosAngle;
double mag = sqrt(x * x + y * y + z * z);
sinAngle = sin ( angle * M_PI / 180.0 );
cosAngle = cos ( angle * M_PI / 180.0 );
if ( mag > 0.0f )
{
double xx, yy, zz, xy, yz, zx, xs, ys, zs;
double oneMinusCos;
double_matrix_t rotMat;
x /= mag;
y /= mag;
z /= mag;
xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * sinAngle;
ys = y * sinAngle;
zs = z * sinAngle;
oneMinusCos = 1.0f - cosAngle;
rotMat[0] = (oneMinusCos * xx) + cosAngle;
rotMat[4] = (oneMinusCos * xy) - zs;
rotMat[8] = (oneMinusCos * zx) + ys;
rotMat[12] = 0.0F;
rotMat[1] = (oneMinusCos * xy) + zs;
rotMat[5] = (oneMinusCos * yy) + cosAngle;
rotMat[9] = (oneMinusCos * yz) - xs;
rotMat[13] = 0.0F;
rotMat[2] = (oneMinusCos * zx) - ys;
rotMat[6] = (oneMinusCos * yz) + xs;
rotMat[10] = (oneMinusCos * zz) + cosAngle;
rotMat[14] = 0.0F;
rotMat[3] = 0.0F;
rotMat[7] = 0.0F;
rotMat[11] = 0.0F;
rotMat[15] = 1.0F;
multiply_double_matrices(in, rotMat, out); // Generic matrix multiplication function.
}
}
I call this function with the same rotations I used to call glRotatef with and in the same order, but the result is different. All rotations are done around the global axes, while glRotatef would rotate around the local axis of in.
For example, I have a plane:
and I pitch up 90 degrees (this gives the expected result with both glRotatef and my rotation function) and persist the transformation:
If I bank 90 degrees with glRotatef (glRotatef(90, 0.0f, 0.0f, 1.0f)), the plane rotates around the transformation's local Z axis pointing out of the plane's nose, which is what I want:
But if I bank 90 degrees with my code (rotate_double_matrix(in, 90.0f, 0.0, 0.0, 1.0, out)), I get this:
The plane is still rotating around the global Z axis.
Similar issues happen if I change the order of rotations - the first rotation gives the expected result, but subsequent rotations still happen around the global axes.
How does glRotatef rotate around a matrix's local axes? What do I need to change in my code to get the same result? I assume rotate_double_matrix needs to modify the x, y, z values passed in based on the in matrix somehow, but I'm not sure.
You're probably multiplying the matrices in the wrong order. Try changing
multiply_double_matrices(in, rotMat, out);
to
multiply_double_matrices(rotMat, in, out);
I can never remember which way is right, and there's a reasonable chance multiply_double_matrices is backwards anyway (at least if I'd written it :)
The order you multiply matrices in matters. Since rotMat holds your rotation, and in holds the combination of all other matrices applied so far, i.e. "everything else", multiplying in the wrong order means that rotMat gets applied after everything else instead of before everything else. (And I didn't get that part backwards! If you want rotMat to be the "top of stack" transformation, that means you actually want it to be the first when your vertex coordinates are processed)
Another possibility is that you mixed up rows with columns. OpenGL matrices go down, then across, i.e.
matrix[0] matrix[4] matrix[8] matrix[12]
matrix[1] matrix[5] matrix[9] matrix[13]
matrix[2] matrix[6] matrix[10] matrix[14]
matrix[3] matrix[7] matrix[11] matrix[15]
even though 2D arrays are traditionally stored across, then down:
matrix[0] matrix[1] matrix[2] matrix[3]
matrix[4] matrix[5] matrix[6] matrix[7]
matrix[8] matrix[9] matrix[10] matrix[11]
matrix[12] matrix[13] matrix[14] matrix[15]
Getting this wrong can cause similar-looking, but mathematically different, issues

Need help fixing an algorithm that approximates pi

I'm trying to write the C code for an algorithm that approximates pi. It's supposed to get the volume of a cube and the volume of a sphere inside that cube (the sphere's radius is 1/2 of the cube's side). Then I am supposed to divide the cube's volume by the sphere's and multiply by 6 to get pi.
It's working but it's doing something weird in the part that is supposed to get the volumes. I figure it's something to do the with delta I chose for the approximations.
With a cube of side 4 instead of giving me a volume of 64 it's giving me 6400. With the sphere instead of 33 it's giving me 3334. something.
Can someone figure it out? Here is the code (I commented the relevant parts):
#include <stdio.h>
int in_esfera(double x, double y, double z, double r_esfera){
double dist = (x-r_esfera)*(x-r_esfera) + (y-r_esfera)*(y-r_esfera) + (z-r_esfera)*(z-r_esfera);
return dist <= (r_esfera)*(r_esfera) ? 1 : 0;
}
double get_pi(double l_cubo){
double r_esfera = l_cubo/2;
double total = 0;
double esfera = 0;
//this is delta, for the precision. If I set it to 1E anything less than -1 the program continues endlessly. Is this normal?
double delta = (1E-1);
for(double x = 0; x < l_cubo; x+=delta){
printf("x => %f; delta => %.6f\n",x,delta);
for(double y = 0; y <l_cubo; y+=delta){
printf("y => %f; delta => %.6f\n",y,delta);
for(double z = 0; z < l_cubo; z+=delta){
printf("z => %f; delta => %.6f\n",z,delta);
total+=delta;
if(in_esfera(x,y,z,r_esfera))
esfera+=delta;
}
}
}
//attempt at fixing this
//esfera/=delta;
//total/=delta;
//
//This printf displays the volumes. Notice how the place of the point is off. If delta isn't a power of 10 the values are completely wrong.
printf("v_sphere = %.8f; v_cube = %.8f\n",esfera,total);
return (esfera)/(total)*6;
}
void teste_pi(){
double l_cubo = 4;
double pi = get_pi(l_cubo);
printf("%.8f\n",pi);
}
int main(){
teste_pi();
}
total+=delta;
if(in_esfera(x,y,z,r_esfera))
esfera+=delta;
total and esfera are three-dimensional volumes whereas delta is a one-dimensional length. If you were tracking units you'd have m3 on the left and m on the right. The units are incompatible.
To fix it, cube delta so that you're conceptually accumulating tiny cubes instead of tiny lines.
total+=delta*delta*delta;
if(in_esfera(x,y,z,r_esfera))
esfera+=delta*delta*delta;
Doing that fixes the output, and also works for any value of delta:
v_sphere = 33.37400000; v_cube = 64.00000000
3.12881250
Note that this algorithm "works" for arbitrary delta values, but it has severe accuracy issues. It's incredibly prone to rounding problems. It works best when delta is a power of two: 1/64.0 is better than 1/100.0, for example:
v_sphere = 33.50365448; v_cube = 64.00000000
3.14096761
Also, if you want your program to run faster get rid of all those printouts! Or at least the ones in the inner loops...
The thing is that multiplication over integers like a * b * c is the same as adding 1 + 1 + 1 + 1 + ... + 1 a * b * c times, right?
You're adding delta + delta + ... (x / delta) * (y / delta) * (z / delta) times. Or, in other words, (x * y * z) / (delta ** 3) times.
Now, that sum of deltas is the same as this:
delta * (1 + 1 + 1 + 1 + ...)
^^^^^^^^^^^^^^^^^^^^ (x * y * z) / (delta**3) times
So, if delta is a power of 10, (x * y * z) / (delta**3) will be an integer, and it'll be equal to the sum of 1's in parentheses (because it's the same as the product x * y * (z / (delta**3)), where the last term is an integer - see the very first sentence of this answer). Thus, your result will be the following:
delta * ( (x * y * z) / (delta ** 3) ) == (x * y * z) / (delta**2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the sum of ones
That's how you ended up calculating the product divided by delta squared.
To solve this, multiply all volumes by delta * delta.
However, I don't think it's possible to use this logic for deltas that aren't a power of 10. And indeed, the code will go all kinds of haywire for delta == 0.21 and l_cubo == 2, for example: you'll get 9.261000000000061 instead of 8.

atan2 for two sinusoids of arbitrary phase shift?

I'm trying to implement an atan2-like function to map two input sinusoidal signals of arbitrary relative phase shift to a single output signal that linearly goes from 0 to 2π. atan2 normally assumes two signals with a 90 deg phase shift.
Given y0(x) = sin(x) and y1 = sin(x + phase), where phase is a fixed non-zero value, how can I implement a way to return x modulo 2π?
atan2 returns the angle of a 2d vector. Your code does not handle such scaling properly. But no worries, it's actually very easy to reduce your problem to an atan2 that would handle everything nicely.
Notice that calculating sin(x) and sin(x + phase) is the same as projecting a point (cos(x), sin(x)) onto the axes (0, 1) and (sin(phase), cos(phase)). This is the same as taking dot products with those axes, or transforming the coordinate system from the standard orthogonal basis into the skewed one. This suggests a simple solution: inverse the transformation to get the coordinates in the orthogonal basis and then use atan2.
Here's a code that does that:
double super_atan2(double x0, double x1, double a0, double a1) {
double det = sin(a0 - a1);
double u = (x1*sin(a0) - x0*sin(a1))/det;
double v = (x0*cos(a1) - x1*cos(a0))/det;
return atan2(v, u);
}
double duper_atan2(double y0, double y1, double phase) {
const double tau = 6.28318530717958647692; // https://tauday.com/
return super_atan2(y0, y1, tau/4, tau/4 - phase);
}
super_atan2 gets the angles of the two projection axes, duper_atan2 solves the problem exactly as you stated.
Also notice that the calculation of det is not strictly necessary. It is possible to replace it by fmod and copysign (we still need the correct sign of u and v).
Derivation:
In code:
// assume phase != k * pi, for any integer k
double f (double y0, double y1, double phase)
{
double u = (- y0 * cos(phase) + y1) / sin(phase);
double v = y0;
double x = atan2 (v, u);
return (x < 0) ? (x + 2 * M_PI) : x;
}

Retaining maximum possible accuracy on float interpolation

We know that points X1 and X2 have respective points Y1 and Y2, so we can calculate Y for any X with:
X - X1 Y - Y1
------- = -------
X2 - X1 Y2 - Y1
We can get simple formula (A) out of that:
Y = (X - X1) * (Y2 - Y1) / (X2 - X1) + Y1;
This should be mathematically equivalent (B):
Y = (X - X1) / (X2 - X1) * (Y2 - Y1) + Y1;
For integer math formula A performs better as long as multiplication (X - X1) * (Y2 - Y1) result stays with in range of the type. Formula B won't work, because if X1 <= X <= X2, then division will always equal 0.
For floating point both should work, but I'm thinking that B would offer better accuracy since multiplication result will remain smaller.
Is my assumption about floating point accuracy correct?
Is there some floating point quirk that I am not taking into consideration?
Assume IEEE 754 floating point representation.
Note 1: I am interested in floating point case, integer math is pretty straight forward.
Note 2: Variables on FP formula may have non-integer values, but NaN and Infs are not within the scope of the question.
To solve the below for Y
X - X1 Y - Y1
------- = -------
X2 - X1 Y2 - Y1
Both (A) and (B) will behave similarity:
(A) Y = (X - offsetX) * deltaY / deltaX + offsetY;
(B) Y = (X - offsetX) / deltaX * deltaY + offsetY;
If points are originally whole numbers, "B ... multiplication result will remain smaller." may hold, but else-wise |deltaX| |deltaY| could both be less than 1 and then this assumption may fail.
To improve accuracy, consider the effects of subtracting 2 numbers (or adding 2 similar numbers that differ in sign). Code could choose X1,Y1 or X2,Y2 as the offset by reversing the roles of point1 and point2. Choosing the offset closest to X,Y will improve accuracy.
With FP math, * and / stress the exponential range allowable by the FP number: The precision of the product can be expected to be within a bit of the mathematically correct answer, but range may overflow.
+ and - stress the precision: The range is rarely an issue, but there may be large cancellation in the significands used to form the sum.
If all co-ordinate values originally are integers, recommend using a 2x wide integer math and deriving the best answer.
If the final result is to be integer-ized, insure code uses a iy = (int) round(Y);
Assuming no underflow or overflow occurs, they should be roughly equivalent in terms of accuracy: both multiplication and division will incur the same relative error, and as the error is roughly multiplicitive, the order in which you perform the operations won't make much difference.
If you know something about the relative magnitudes of the terms involved, you might be able to rearrange terms such that the subtractions are exact, which might reduce the error slightly.
In general, multiplications and divisions rarely cause a significant loss of precision. Because these are floating point numbers, with separate fields for the scale and significant digits, getting large intermediate results in itself isn't an issue. 2e100/3e100 and 2/3 are (for all intents and purposes) equally accurate.
On the other hand, additions or subtractions with a result much smaller in magnitude than the operands are much more common causes of loss of precision.
With this in mind, the two forms are basically equivalent. If your numbers are 'mainstream' (i.e. multiplication doesn't cause over/underflow), then you won't encounter any problems with either form. If you can't assume your numbers are mainstream, then you have to take all kinds of special precautions to get a good result.
Now, rather than consider the two forms (A) and (B), I would suggest selecting between (A) and (C):
Y = (X - X1) * (Y2 - Y1) / (X2 - X1) + Y1; (A)
Y = (X - X2) * (Y2 - Y1) / (X2 - X1) + Y2; (C)
and choosing the form for which the first factor X - X1 or X - X2 is smaller in magnitude. That way, if Y turns out to be small, you minimize the loss of precision.
For example, let's use
(X1,Y1) = (-100, -100)
(X2,Y2) = (0, 0)
X = 0.76
with three digits of precision. Then we get for (A):
Y = (0.76 - -100) * (0 - -100) / (0 - -100) + -100
= 101 * 100 / 100 - 100
= 1
while for (C), we get:
Y = (0.76 - 0) * (0 - -100) / (0 - -100) + 0
= 0.76 * 100 / 100 + 0
= 0.76
So, the quick answer to your question is:
Size of intermediate results in itself doesn't matter. It is not a reason to prefer (B) over (A).
Always consider addition and subtraction as more likely sources of loss of precision.

find if 4 points on a plane form a rectangle?

Can somebody please show me in C-style pseudocode how to write a function (represent the points however you like) that returns true if 4-points (args to the function) form a rectangle, and false otherwise?
I came up with a solution that first tries to find 2 distinct pairs of points with equal x-value, then does this for the y-axis. But the code is rather long. Just curious to see what others come up with.
find the center of mass of corner points: cx=(x1+x2+x3+x4)/4, cy=(y1+y2+y3+y4)/4
test if square of distances from center of mass to all 4 corners are equal
bool isRectangle(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
double cx,cy;
double dd1,dd2,dd3,dd4;
cx=(x1+x2+x3+x4)/4;
cy=(y1+y2+y3+y4)/4;
dd1=sqr(cx-x1)+sqr(cy-y1);
dd2=sqr(cx-x2)+sqr(cy-y2);
dd3=sqr(cx-x3)+sqr(cy-y3);
dd4=sqr(cx-x4)+sqr(cy-y4);
return dd1==dd2 && dd1==dd3 && dd1==dd4;
}
(Of course in practice testing for equality of two floating point numbers a and b should be done with finite accuracy: e.g. abs(a-b) < 1E-6)
struct point
{
int x, y;
}
// tests if angle abc is a right angle
int IsOrthogonal(point a, point b, point c)
{
return (b.x - a.x) * (b.x - c.x) + (b.y - a.y) * (b.y - c.y) == 0;
}
int IsRectangle(point a, point b, point c, point d)
{
return
IsOrthogonal(a, b, c) &&
IsOrthogonal(b, c, d) &&
IsOrthogonal(c, d, a);
}
If the order is not known in advance, we need a slightly more complicated check:
int IsRectangleAnyOrder(point a, point b, point c, point d)
{
return IsRectangle(a, b, c, d) ||
IsRectangle(b, c, a, d) ||
IsRectangle(c, a, b, d);
}
1. Find all possible distances between given 4 points. (we will have 6 distances)
2. XOR all distances found in step #1
3. If the result after XORing is 0 then given 4 points are definitely vertices of a square or a rectangle otherwise, return false (given 4 points do not form a rectangle).
4. Now, to differentiate between square and rectangle
a. Find the largest distance out of 4 distances found in step #1.
b. Check if the largest distance / Math.sqrt (2) is equal to any other distance.
c. If answer is No, then given four points form a rectangle otherwise they form a square.
Here, we are using geometric properties of rectangle/square and Bit Magic.
Rectangle properties in play
Opposite sides and diagonals of a rectangle are of equal length.
If the diagonal length of a rectangle is sqrt(2) times any of its length, then the rectangle is a square.
Bit Magic
XORing equal value numbers return 0.
Since distances between 4 corners of a rectangle will always form 3 pairs, one for diagonal and two for each side of different length, XORing all the values will return 0 for a rectangle.
translate the quadrilateral so that one of its vertices now lies at the origin
the three remaining points form three vectors from the origin
one of them must represent the diagonal
the other two must represent the sides
by the parallelogram rule if the sides form the diagonal, we have a parallelogram
if the sides form a right angle, it is a parallelogram with a right angle
opposite angles of a parallelogram are equal
consecutive angles of a parallelogram are supplementary
therefore all angles are right angles
it is a rectangle
it is much more concise in code, though :-)
static bool IsRectangle(
int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4)
{
x2 -= x1; x3 -= x1; x4 -= x1; y2 -= y1; y3 -= y1; y4 -= y1;
return
(x2 + x3 == x4 && y2 + y3 == y4 && x2 * x3 == -y2 * y3) ||
(x2 + x4 == x3 && y2 + y4 == y3 && x2 * x4 == -y2 * y4) ||
(x3 + x4 == x2 && y3 + y4 == y2 && x3 * x4 == -y3 * y4);
}
(If you want to make it work with floating point values, please, do not just blindly replace the int declarations in the headers. It is bad practice. They are there for a reason. One should always work with some upper bound on the error when comparing floating point results.)
The distance from one point to the other 3 should form a right triangle:
| / /|
| / / |
| / / |
|/___ /___|
d1 = sqrt( (x2-x1)^2 + (y2-y1)^2 )
d2 = sqrt( (x3-x1)^2 + (y3-y1)^2 )
d3 = sqrt( (x4-x1)^2 + (y4-y1)^2 )
if d1^2 == d2^2 + d3^2 then it's a rectangle
Simplifying:
d1 = (x2-x1)^2 + (y2-y1)^2
d2 = (x3-x1)^2 + (y3-y1)^2
d3 = (x4-x1)^2 + (y4-y1)^2
if d1 == d2+d3 or d2 == d1+d3 or d3 == d1+d2 then return true
If the points are A, B, C & D and you know the order then you calculate the vectors:
x=B-A, y=C-B, z=D-C and w=A-D
Then take the dot products (x dot y), (y dot z), (z dot w) and (w dot x). If they are all zero then you have a rectangle.
We know that two staright lines are perpendicular if product of their slopes is -1,since a plane is given we can find the slopes of three consecutive lines and then multiply them to check if they are really perpendicular or not. Suppose we have lines L1,L2,L3. Now if L1 is perpendicular to L2 and L2 perpendicular to L3, then it is a rectangle and slope of the m(L1)*m(L2)=-1 and m(L2)*m(L3)=-1, then it implies it is a rectangle. The code is as follows
bool isRectangle(double x1,double y1,
double x2,double y2,
double x3,double y3,
double x4,double y4){
double m1,m2,m3;
m1 = (y2-y1)/(x2-x1);
m2 = (y2-y3)/(x2-x3);
m3 = (y4-y3)/(x4-x3);
if((m1*m2)==-1 && (m2*m3)==-1)
return true;
else
return false;
}
taking the dot product suggestion a step further, check if two of the vectors made by any 3 of the points of the points are perpendicular and then see if the x and y match the fourth point.
If you have points [Ax,Ay] [Bx,By] [Cx,Cy] [Dx,Dy]
vector v = B-A
vector u = C-A
v(dot)u/|v||u| == cos(theta)
so if (v.u == 0) there's a couple of perpendicular lines right there.
I actually don't know C programming, but here's some "meta" programming for you :P
if (v==[0,0] || u==[0,0] || u==v || D==A) {not a rectangle, not even a quadrilateral}
var dot = (v1*u1 + v2*u2); //computes the "top half" of (v.u/|v||u|)
if (dot == 0) { //potentially a rectangle if true
if (Dy==By && Dx==Cx){
is a rectangle
}
else if (Dx==Bx && Dy==Cy){
is a rectangle
}
}
else {not a rectangle}
there's no square roots in this, and no potential for a divide by zero. I noticed people mentioning these issues on earlier posts so I thought I'd offer an alternative.
So, computationally, you need four subtractions to get v and u, two multiplications, one addition and you have to check somewhere between 1 and 7 equalities.
maybe I'm making this up, but i vaguely remember reading somewhere that subtractions and multiplications are "faster" calculations. I assume that declaring variables/arrays and setting their values is also quite fast?
Sorry, I'm quite new to this kind of thing, so I'd love some feedback to what I just wrote.
Edit: try this based on my comment below:
A = [a1,a2];
B = [b1,b2];
C = [c1,c2];
D = [d1,d2];
u = (b1-a1,b2-a2);
v = (c1-a1,c2-a2);
if ( u==0 || v==0 || A==D || u==v)
{!rectangle} // get the obvious out of the way
var dot = u1*v1 + u2*v2;
var pgram = [a1+u1+v1,a2+u2+v2]
if (dot == 0 && pgram == D) {rectangle} // will be true 50% of the time if rectangle
else if (pgram == D) {
w = [d1-a1,d2-a2];
if (w1*u1 + w2*u2 == 0) {rectangle} //25% chance
else if (w1*v1 + w2*v2 == 0) {rectangle} //25% chance
else {!rectangle}
}
else {!rectangle}
I recently faced a similar challenge, but in Python, this is what I came up with in Python, perhaps this method may be valuable. The idea is that there are six lines, and if created into a set, there should be 3 unique line distances remaining - the length, width, and diagonal.
def con_rec(a,b,c,d):
d1 = a.distanceFromPoint(b)
d2 = b.distanceFromPoint(c)
d3 = c.distanceFromPoint(d)
d4 = d.distanceFromPoint(a)
d5 = d.distanceFromPoint(b)
d6 = a.distanceFromPoint(c)
lst = [d1,d2,d3,d4,d5,d6] # list of all combinations
of point to point distances
if min(lst) * math.sqrt(2) == max(lst): # this confirms a square, not a rectangle
return False
z = set(lst) # set of unique values in ck
if len(lst) == 3: # there should be three values, length, width, diagonal, if a
4th, it's not a rectangle
return True
else: # not a rectangle
return False
How about to verify those 4 points could form a parallelogram first, then finding out if there exists one right angle.
1. verify parallelogram
input 4 points A, B, C, D;
if(A, B, C, D are the same points), exit;// not a rectangle;
else form 3 vectors, AB, AC, AD, verify(AB=AC+AD || AC=AB+AD || AD=AB+AC), \\if one of them satisfied, this is a parallelogram;
2.verify a right angle
through the last step, we could find which two points are the adjacent points of A;
We need to find out if angle A is a right angle, if it is, then rectangle.
I did not know if there exist bugs. Please figure it out if there is.
Here is my algorithm proposal, for an axis-aligned rectangle test, but in Python.
The idea is to grab the first point as a pivot, and that all the other points must conform to the same width and height, and checks that all points are distinct, via a set, to account for cases such as (1, 2), (1, 2), (10, 30), (10, 30).
from collections import namedtuple
Point = namedtuple('Point', ('x', 'y'))
def is_rectangle(p1, p2, p3, p4) -> bool:
width = None
height = None
# All must be distinct
if (len(set((p1, p2, p3, p4))) < 4):
return False
pivot = p1
for point in (p2, p3, p4):
candidate_width = point.x - pivot.x
candidate_height = point.y - pivot.y
if (candidate_width != 0):
if (width is None):
width = candidate_width
elif (width != candidate_width):
return False
if (candidate_height != 0):
if (height is None):
height = candidate_height
elif (height != candidate_height):
return False
return width is not None and height is not None
# Some Examples
print(is_rectangle(Point(10, 50), Point(20, 50), Point(10, 40), Point(20, 40)))
print(is_rectangle(Point(100, 50), Point(20, 50), Point(10, 40), Point(20, 40)))
print(is_rectangle(Point(10, 10), Point(20, 50), Point(10, 40), Point(20, 40)))
print(is_rectangle(Point(10, 30), Point(20, 30), Point(10, 30), Point(20, 30)))
print(is_rectangle(Point(10, 30), Point(10, 30), Point(10, 30), Point(10, 30)))
print(is_rectangle(Point(1, 2), Point(10, 30), Point(1, 2), Point(10, 30)))
print(is_rectangle(Point(10, 50), Point(80, 50), Point(10, 40), Point(80, 40)))

Resources