Related
Let's say I have two intervals,
[a1, a2] and [b1, b2]
Where a1,a2,b1,b2 are all in the range [0, 2 pi]. Now, given these two intervals, I want to find their overlapping interval. This is quite tricky. Since an example of two intervals is
[5, 1] and [0, 6]
Which are sketched below (the red areas are the intervals).
Notice that these two intervals return an overlapping interval that consists of two intervals:
[0,1] and [5,6]
There are multiple different cases that must be treated, is there any known algorithm that does this?
I do not know of an existing algorithm (which doesn't mean there isn't one), but here's one I've come up with.
As already mentioned by #Michael Kenzel, numbers don't increase monotonically, which makes everything very complicated.
But we can observe that we can unroll the circle onto the number line.
Then each interval then appears infinitely many times with a period of 2π.
Let's first define a normalize operation as following:
normalize([a, b]):
if (a > b):
a -= 2π
Using this operation we unroll both our intervals onto a [-2π, 2π] part of the number line.
Example intervals:
[2, 5] -> [2, 5]
[4, π] -> [-2, π]
Two intervals on a circle can overlap at most 2 times.
(I don't have a proper proof of this, but intuitively: an overlap starts where one interval started and another one has not ended. This can happen only once on a number line and twice in our case.)
By just looking at normalized intervals, we can miss one of the overlaps. In the example above we would detect [2, π] overlap and miss [4, 5]. This happens because we have unrolled the original intervals not onto [0, 2π], but a twice longer part of the number line, [-2π, 2π].
To correct for that, we can, for example, take the part that falls onto the negative line and shift it by 2π to the right, this way having all pieces in the original [0, 2π]. But it is computationally ineffective, as we will, in the worst case, have to test 2 pieces on one interval against 2 pieces of another interval - total of 4 operations.
Here is an illustration of such an unlucky example that will require 4 comparisons:
If we want to be a bit more efficient, we will try to do only 2 interval-vs-interval operations. We won't need more as there will be at most 2 overlaps.
As the intervals repeat infinitely on the number line with the period of 2π, we can just take 2 neighboring duplicates of the first interval and compare them against the second one.
To make sure that the second interval will be, so to say, in between those duplicates, we can take the one that starts earlier and add 2π to its both ends. Or subtract 2π from the one that starts later.
There will be not more than two overlaps, which can be then brought back to the [0, 2π] interval by addition/subtraction of 2π.
In our original example it would look like that:
To sum it up:
given [a, b], [c, d]
[A, B] = normalize([a, b])
[C, D] = normalize([c, d])
if (A < C):
[E, F] = [A + 2π, B + 2π]
else:
[E, F] = [A - 2π, B - 2π]
I_1 = intersect([A, B], [C, D])
I_2 = intersect([E, F], [C, D])
bring I_1 and I_2 back to the [0, 2π]
I think I didn't miss any corner cases, but feel free to point to any mistake in my logic.
The first thing to note is that no new angle is created when you discover the intersection sector of two sectors 'A' and 'B'. Sector 'A' is defined by two limits, Acw and Acc, and sector 'B' is defined by two limits, Bcw and Bcc. Here 'cw' and 'cc' denote 'ClockWise' and 'CounterClockwise'.
The boundaries of the intersection sector will be made from at most two out of these four angles. The problem is entirely concerned with selecting two out of these four angles to be the limits of the intersection sector, let's call them Icw and Icc.
It is important to distinguish between "cw" and "cc" and keep them straight throughout the problem, because any pair of angles actually defines two sectors, right? This is as you have shown in your picture at the top of this answer. The issue of "angle wraparound" will arise naturally as the problem is solved.
Some Helper Functions
OK, so we have our four angles, and we have to select two of the four to be the limits of our intersection sector. In order to do this, we need an operator that determines whether an angle dAngle falls between two limits, let's call them dLimitCW and dLimitCC.
It is in this operator that the issue of "angle wraparound" arises. I did mine by constraining all angles to the range of -π to π. To determine whether dAngle falls between dLimitCW and dLimitCC, we subtract dAngle from dLimitCW and dLimitCC, and then constrain the result to fall within the [-π, π] range by adding or subtracting 2π. This is just like rotating the polar coordinate system by the angle dAngle , so that what was dAngle is now zero; what was dLimitCW is now dLimitCWRot, and what was dLimitCC is now dLimitCCRot.
The code looks like this:
bool AngleLiesBetween(double dAngle, double dLimitCW, double dLimitCC)
{
double dLimitCWRot, dLimitCCRot;
// Rotate everything so that dAngle is on zero axis.
dLimitCWRot = constrainAnglePlusMinusPi(dLimitCW - dAngle);
dLimitCCRot = constrainAnglePlusMinusPi(dLimitCC - dAngle);
if (dLimitCWRot > dLimitCCRot)
return (signbit(dLimitCWRot * dLimitCCRot));
else
return (!signbit(dLimitCWRot * dLimitCCRot));
}
where the function constrainAnglePlusMinusPi is
double constrainAnglePlusMinusPi(double x)
{
x = fmod(x + pi, 2*pi);
if (x < 0)
x += 2*pi;
return x - pi;
}
Once we have our "angle lies between" function, we use it to select which of the four angles that made up the limit angles of the two sectors make up the intersection sector.
The Nitty Gritty
To do this, we must first detect the case in which the two angular ranges do not overlap; we do this by calling our AngleLiesBetween() function four times; if it returns a "false" all four times, there is no overlap and the intersection is undefined:
if (
(AngleLiesBetween(Acw, Bcw, Bcc) == false) &&
(AngleLiesBetween(Acc, Bcw, Bcc) == false) &&
(AngleLiesBetween(Bcw, Acw, Acc) == false) &&
(AngleLiesBetween(Bcc, Acw, Acc) == false)
)
{
// Ranges have no overlap, result is undefined.
*this = CAngleRange(0.0f, 0.0f);
return;
}
Here I'm returning a CAngleRange object containing (0.0, 0.0) to indicate "no overlap," but you can do it some other way if you like, like by having your "intersection" function return a value of "false."
Once you've handled the "no overlap" case, the rest is easy. You check each of the six remaining combinations one at a time and determine which two limits are the limits of I by their outcomes:
if ((AngleLiesBetween(Acw, Bcw, Bcc) == true) && (AngleLiesBetween(Acc, Bcw, Bcc) == false)) then Icw = Acw and Icc = Bcc;
if ((AngleLiesBetween(Acw, Bcw, Bcc) == false) && (AngleLiesBetween(Acc, Bcw, Bcc) == true)) then Icw = Bcw and Icc = Acc;
if ((AngleLiesBetween(Acw, Bcw, Bcc) == true) && (AngleLiesBetween(Acc, Bcw, Bcc) == true)) then Icw = Acw and Icc = Acc;
if ((AngleLiesBetween(Bcw, Acw, Acc) == true) && (AngleLiesBetween(Bcc, Acw, Acc) == false)) then Icw = Bcw and Icc = Acc;
if ((AngleLiesBetween(Bcw, Acw, Acc) == false) && (AngleLiesBetween(Bcc, Acw, Acc) == true)) then Icw = Acw and Icc = Bcc;
and finally
if ((AngleLiesBetween(Bcw, Acw, Acc) == true) && (AngleLiesBetween(Bcc, Acw, Acc) == true)) then Icw = Bcw and Icc = Bcc.
You don't have to constrain the results to [-π, π] or [0, 2π] because you haven't changed them; each of the result angles is just one of the angles you presented as input to the function in the first place.
Of course you can optimize and streamline the code I've given in various ways that take advantage of the symmetries inherent in the problem, but I think when all is said and done you have to compare eight separate angle combinations for "between-ness" no matter how you optimize things. I like to keep things simple and straightforward in my code, in case I have made an error and have to come back and debug it in a few years when I've forgotten all my clever optimizations and streamlining efforts.
About Angle Wraparound
Notice that the issue of "angle wraparound" got handled in function AngleLiesBetween(); we rotated the coordinate system to put the angle we are checking (which we called dAngle) for "between-ness" at zero degrees. This naturally puts the two angle limits (dLimitCW and dLimitCC) on either side of the polar origin in the case in which dAngle is between those limits. Thus, the wrap-around issue disappears; we've "rotated it out of the way," so to speak.
About the Polar Coordinate System
It may be worth noting that in the polar coordinate system I'm using, CW angles are more positive than CC angles (unless the wrap-around is between them). This is the opposite of the polar coordinates we learn in calculus class, where angles increase in the CC (counterclockwise) direction.
This is because of the quandary that results from the decision — made long, long ago — that computer devices (like display surfaces, originally implemented on cathode ray tubes) would count the vertical direction as increasing downward, instead of upward, as we learn when we study analytic geometry and calculus.
The people who made this decision did so because they wanted to display text on the screen, with the "first" line at the top, the "second" line (line 2) below the "first" line (line 1), and so forth. To make this easier to keep track of this in their early machine code, they had the +y direction go "down," as in "toward the bottom of the screen." This decision has far-reaching and annoying consequences for people who do image geometry in code.
One of the consequences is that by flipping the direction of +y, the "sense of rotation" also flipped from the conventional "angle increases counterclockwise" sense we're used to from high-school math. This issue is mostly invisible at the coding level; it only comes out when you look at your results on the screen.
This is why it is very important to write code to visualize your results on the screen before you trust them. Here "trust them" is certainly necessary before you let your customer see them.
As long as you have intervals where the numbers just increase monotonically, it's simple; you just take the max() of the minimums and the min() of the maximums and done. It would seem that the major source of complication here is the fact that you can have intervals that wrap around at 0, i.e., the numbers that are part of the interval are not monotonically-increasing. It would seem to me that one way around this problem is to simply treat intervals that wrap around as not one interval but the union of two intervals. For example, think of [5, 1] as [5, 2 pi] ∪ [0, 1]. Then the problem of finding the intersection of [5, 1] and [0, 6] turns into the problem of finding the intersection of [5, 2 pi] and [0, 6] as well as the intersection of [0, 1] and [0, 6]. Mathematically, you'd be taking advantage of the distributive law of set intersection, i.e., (A ∪ B) ∩ C = (A ∩ C) ∪ (B ∩ C). So given two intervals A and B, we would start by splitting each into two intervals, A1 and A2, and B1 and B2, where A1 and B1 each start after 0 and end before 2 pi, and A2 and B2 start before 2 pi and end before 2 pi. Slitting like this, we can compute our intersections like
(A1 ∪ A2) ∩ (B1 ∪ B2) = (A1 ∩ (B1 ∪ B2)) ∪ (A2 ∩ (B1 ∪ B2) = (A1 ∩ B1) ∪ (A1 ∩ B2) ∪ (A2 ∩ B1) ∪ (A2 ∩ B2)
i.e., compute the intersection of all combinations of A1, A2, B1, and B2…
The problem sounds like this: we get n-cubes. Each cube has a length (the edge's length) and a colour. The edges' lengths are distinct, but the culours are not, for instance: any two cubes can never have the same length, but it is possible to have the same colour. The colours are from 1 to p (p is given).
We have to build a cube-tower that has a maximum height, following these rules:
1) a cube cannot be placed upon a cube if they have the same colour;
2) a cube cannot pe placed upon a cube whose edge's length is smaller.
e.g: cube c1 has a length of 3, cube c2 has a length of 5. cube c1 can be placed on the top of c2, but cube c2 cannot be placed on the top of c1.
Alright, so the algorithm I came up with in order to solve this problem is this:
we sort the cubes by edge length, in descending order and we put them in an array;
we add the first cube in the array to the Tower;
we save the length of the last inserted cube( in this case, the first cube's length ) in variable l;
we save the colour of the last inserted cube( in this case, the first cube's colour ) in variable c;
we go through the rest of the array, inserting the first cube whose length is smaller than l and colour different than c and then we repeat 3-4-5;
Now what I'm having difficulties with is, how do I prove this greedy algorithm to be the optimal one? I guess that the proof has to somehow look like the ones here: http://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/04GreedyAlgorithmsI-2x2.pdf
The question is:
Is there a case where picking the max-length cube is not optimal?
At each decision-node we have to decide if we pick a or b, given a>b:
Assume picking b is strictly optimal (implies max-height):
Case 1: col(a) == col(b)
b optimal => final tower: b, x0, x1, ...
but also valid by construction with equal height: a, x0, x1, ...
valid because: col(a) == col(b), (a > b) & (b > x0) => (a > x0) (transitivity)
contradiction!
Case 2 col(a) != col(b)
b optimal -> final tower: b, x0, x1, ...
but also valid construction with more height: a, b, x0, x1, ...
valid because: (a > b) & (col(a) != col(b)) => a before b
contradiction!
We assumed picking b is strictly optimal and showed contradictions. Picking b can only be equally good or worse than picking a (the max-length cube of the remaining ones).
Mr. Dum: Hello, I'm very stupid but I still want to solve a 3x3x3 Rubik's cube.
Mr. Smart: Well, you're in luck. Here is guidance to do just that!
Mr. Dum: No that won't work for me because I'm Dum. I'm only capable of following an algorithm like this.
pick up cube
look up a list of moves from some smart person
while(cube is not solved)
perform the next move from list and turn
the cube as instructed. If there are no
more turns in the list, I'll start from the
beginning again.
hey look, it's solved!
Mr. Smart: Ah, no problem here's your list!
Ok, so what sort of list would work for a problem like this? I know that the Rubik's cube can never be farther away from 20 moves to solved, and that there are 43,252,003,274,489,856,000 permutations of a Rubik's Cube. Therefore, I think that this list could be (20 * 43,252,003,274,489,856,000) long, but
Does anyone know the shortest such list currently known?
How would you find a theoretically shortest list?
Note that this is purely a theoretical problem and I don't actually want to program a computer to do this.
An idea to get such a path through all permutations of the Cube would be to use some of the sequences that human solvers use. The main structure of the algorithm for Mr Smart would look like this:
function getMoves(callback):
paritySwitchingSequences = getParitySwitchingSequences()
cornerCycleSequences = getCornerCycleSequences()
edgeCycleSequences = getEdgeCycleSequences()
cornerRotationSequences = getCornerRotationSequences()
edgeFlipSequences = getEdgeFlipSequences()
foreach paritySeq in paritySwitchingSequences:
if callback(paritySeq) return
foreach cornerCycleSeq in cornerCycleSequences:
if callback(cornerCycleSeq) return
foreach edgeCycleSeq in edgeCycleSequences:
if callback(edgeCycleSeq) return
foreach cornerRotationSeq in cornerRotationSequences:
if callback(cornerRotationSeq) return
foreach edgeFLipSeq in edgeFlipSequences:
if callback(edgeFlipSeq) return
The 5 get... functions would all return an array of sequences, where each sequence is an array of moves. A callback system will avoid the need for keeping all moves in memory, and could be rewritten in the more modern generator syntax if available in the target language.
Mr Dumb would have this code:
function performMoves(sequence):
foreach move in sequence:
cube.do(move)
if cube.isSolved() then return true
return false
getMoves(performMoves)
Mr Dumb's code passes his callback function once to Mr Smart, who will then keep calling back that function until it returns true.
Mr Smart's code will go through each of the 5 get functions to retrieve the basic sequences he needs to start producing sequences to the caller. I will describe those functions below, starting with the one whose result is used in the innermost loop:
getEdgeFlipSequences
Imagine a cube that has all pieces in their right slots and rightly rotated, except for the edges which could be flipped, but still in right slot. If they would be flipped, the cube would be solved. As there are 12 edges, but edges can only be flipped with 2 at the same time, the number of ways this cube could have its edges flipped (or not) is 2^11 = 2048. Otherwise put, there are 11 of the 12 edges that can have any flip status (flipped or not), while the last one is bound by the flips of the other 11.
This function should return just as many sequences, such that after applying one of those sequences the next state of the cube is produced that has a unique set of edges flipped.
function getEdgeFlipSequences
sequences = []
for i = 1 to 2^11:
for edge = 1 to 11:
if i % (2^edge) != 0 then break
sequence = getEdgePairFlipSequence(edge, 12)
sequences.push(sequence)
return sequences
The inner loop makes sure that with one flip in each iteration of the outer loop you get exactly all possible flip states.
It is like listing all numbers in binary representation by just flipping one bit to arrive at the next number. The numbers' output will not be in order when produced that way, but you will get them all. For example, for 4 bits (instead of 11), it would go like this:
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000
The sequence will determine which edge to flip together with the 12th edge. I will not go into defining that getEdgePairFlipSequence function now. It is evident that there are sequences for flipping any pair of edges, and where they are not publicly available, one can easily make a few moves to bring those two edges in a better position, do the double flip and return those edges to their original position again by applying the starting moves in reversed order and in opposite direction.
getCornerRotationSequences
The idea is the same as above, but now with rotated corners. The difference is that a corner can have three rotation states. But like with the flipped edges, if you know the rotations of 7 corners (already in their right position), the rotation of the 8th corner is determined as well. So there are 3^7 possible ways a cube can have its corners rotated.
The trick to rotate a corner together with the 8th corner, and so find all possible corner rotations also works here. The pattern in the 3-base number representation would be like this (for 3 corners):
000
001
002
012
011
010
020
021
022
122
121
120
110
111
112
102
101
100
200
201
202
212
211
210
220
221
222
So the code for this function would look like this:
function getCornerRotationSequences
sequences = []
for i = 1 to 3^7:
for corner = 1 to 7:
if i % (3^edge) != 0 break
sequence = getCornerPairRotationSequence(corner, 8)
sequences.push(sequence)
return sequences
Again, I will not define getCornerPairRotationSequence. A similar reasoning as for the edges applies.
getEdgeCycleSequences
When you want to move edges around without affecting the rest of the cube, you need to cycle at least 3 of them, as it is not possible to swap two edges without altering anything else.
For instance, it is possible to swap two edges and two corners. But that would be out of the scope of this function. I will come back to this later when dealing with the last function.
This function aims to find all possible cube states that can be arrived at by repeatedly cycling 3 edges. There are 12 edges, and if you know the position of 10 of them, the positions of the 2 remaining ones are determined (still assuming corners remain at their position). So there are 12!/2 = 239 500 800 possible permutations of edges in these conditions.
This may be a bit of problem memory-wise, as the array of sequences to produce will occupy a multiple of that number in bytes, so we could be talking about a few gigabytes. But I will assume there is enough memory for this:
function getEdgeCycleSequences
sequences = []
cycles = getCyclesReachingAllPermutations([1,2,3,4,5,6,7,8,9,10,11,12])
foreach cycle in cycles:
sequence = getEdgeTripletCycleSequence(cycle[0], cycle[1], cycle[3])
sequences.push(sequence)
return sequences
The getCyclesAchievingAllPermutations function would return an array of triplets of edges, such that if you would cycle the edges from left to right as listed in a triplet, and repeat this for the complete array, you would get to all possible permutations of edges (without altering the position of corners).
Several answers for this question I asked can be used to implement getCyclesReachingAllPermutations. The pseudo code based on this answer could look like this:
function getCyclesReachingAllPermutations(n):
c = [0] * n
b = [0, 1, ... n]
triplets = []
while (true):
triplet = [0]
for (parity = 0; parity < 2; parity++):
for (k = 1; k <= c[k]; k++):
c[k] = 0
if (k == n - 1):
return triplets
c[k] = c[k] + 1
triplet.add( b[k] )
for (j = 1, k--; j < k; j++, k--):
swap(b, j, k)
triplets.add(triplet)
Similarly for the other main functions, also here is a dependency on a function getEdgeTripletCycleSequence, which I will not expand on. There are many known sequences to cycle three edges, for several positions, and others can be easily derived from them.
getCornerCycleSequences
I will keep this short, as it is the same thing as for edges. There are 8!/2 possible permutations for corners if edges don't move.
function getCornerCycleSequences
sequences = []
cycles = getCyclesReachingAllPermutations([1,2,3,4,5,6,7,8])
foreach cycle in cycles:
sequence = getCornerTripletCycleSequence(cycle[0], cycle[1], cycle[3])
sequences.push(sequence)
return sequences
getParitySwitchingSequences
This extra level is needed to deal with the fact that a cube can be in an odd or even position. It is odd when an odd number of quarter-moves (a half turn counts as 2 then) is needed to solve the cube.
I did not mention it before, but all the above used sequences should not change the parity of the cube. I did refer to it implicitly when I wrote that when permuting edges, corners should stay in their original position. This ensures that the parity does not change. If on the other hand you would apply a sequence that swaps two edges and two corners at the same time, you are bound to toggle the parity.
But since that was not accounted for with the four functions above, this extra layer is needed.
The function is quite simple:
function getParitySwitchingSequences
return = [
[L], [-L]
]
L is a constant that represents the quarter move of the left face of the cube, and -L is the same move, but reversed. It could have been any face.
The simplest way to toggle the parity of a cube is just that: perform a quarter move.
Thoughts
This solution is certainly not the optimal one, but it is a solution that will eventually go through all states of the cube, albeit with many duplicate statuses appearing along the way. And it will do so with less than 20 moves between two consecutive permutations. The number of moves will vary between 1 -- for parity toggle -- and 18 -- for flipping two edges allowing for 2 extra moves to bring an edge in a good relative position and 2 for putting that edge back after the double flip with 14 moves, which I think is the worst case.
One quick optimisation would be to put the parity loop as the inner loop, as it only consists of one quarter move it is more efficient to have that one repeated the most.
Hamilton Graph: the best
A graph has been constructed where each edge represents one move, and where the nodes represent all unique cube states. It is cyclic, such that the edge forward from the last node, brings you back to the first node.
So this should allow you to go through all cube states with as many moves. Clearly a better solution cannot exist. The graph can be downloaded.
You can use the De Bruijn sequence to get a sequence that will definitely solve a rubik's cube (because it will contain every possible permutation of size 20).
From wiki (Python):
def de_bruijn(k, n):
"""
De Bruijn sequence for alphabet k
and subsequences of length n.
"""
try:
# let's see if k can be cast to an integer;
# if so, make our alphabet a list
_ = int(k)
alphabet = list(map(str, range(k)))
except (ValueError, TypeError):
alphabet = k
k = len(k)
a = [0] * k * n
sequence = []
def db(t, p):
if t > n:
if n % p == 0:
sequence.extend(a[1:p + 1])
else:
a[t] = a[t - p]
db(t + 1, p)
for j in range(a[t - p] + 1, k):
a[t] = j
db(t + 1, t)
db(1, 1)
return "".join(alphabet[i] for i in sequence)
You can use it kinda like this:
print(de_bruijn(x, 20))
Where 20 is the size of your sequence and x is a list/string containing every possible turn (couldn't think of a better word) of the cube.
While giving an answer to "Given four coordinates check whether it forms a square", I came across this answer, which checks for a parallelogram, then for a right angle. This works, but only if the points coming in are in a certain order. Namely, P1 and P3 must be "opposite" from each other, not adjacent.
So, the question. If the four points coming in could be in any order, how can you sort them so that they are in the "correct" order to make a quadrilateral?
The simplest thing I could come up with is something like:
for each valid permutation of points[]{ // see below for "valid"
generate line segment for:
points[0] -> points[1]
points[1] -> points[2]
points[2] -> points[3]
points[3] -> points[0]
if any line segment crosses another // use cross product
continue
return permutation
}
I know that most permutations are simple rotations(0123 == 1230), so I can keep the first point 'fixed'. Also, I think I could cut it down by only considering what points are in the 0 and 2 of each permutation spots, since the order of the other two don't matter. For example, if 0123 is a polygon, 0321 is also, since it generates the same segments.
This leaves me with only three basic permutations to check:
[0,1,2,3]
[0,1,3,2]
[0,2,1,3]
Each permutation has six segment-to-segment checks, so that's a total of 18 comparisons.
I can't think of any other way to do this, but it seems like I'm missing something. Is there a better way to do this? The answer given for the square question is good, but if I have to make an additional(up to) 18 checks to verify the points are in the correct order, it would be quicker just to use inter-corner distances.
Each permutation has six segment-to-segment checks, so that's a total of 18 comparisons.
You do not need to check all the segments: it would be sufficient to check that segments [0-2] and [1-3] (i.e. the two diagonals) intersect. You need to check that the segments intersect, not the lines to which the segments belong, i.e. an intersection outside of the segments does not count.
Once you fix the starting point "A", you end up with six possible permutations:
Two of them (A-B-D-C and A-C-D-B) are good; the remaining four are bad. You can arrive at a good one with only two checks:
Check the initial permutation; if it is good, keep it; otherwise
Swap points 1 and 2, and check the permutation; if it is good, keep it; otherwise
Revert to the original permutation, swap points 2 and 3, and keep that permutation; it is bound to be "good".
Implement a method called isParallelAndEqual(p0,p1,q0,q1). This checks if lines p1-p1 and q0-q1 are parallel and of equal length.
Given points a,b,c and d, the final result looks like:
ifParallelAndEqual(a,b,c,d)||ifParallelAndEqual(a,c,b,d)
Can't you simply check all of the below until you find one that's true? (that is, check P1 opposite each other point)
P3 = P1 + (P2-P1) + (P4-P1)
P2 = P1 + (P3-P1) + (P4-P1)
P4 = P1 + (P2-P1) + (P3-P1)
For the square variant, if they happen to be axis-aligned, you can do: (that is, the opposite point is the one which doesn't have a coordinate in common)
if (P1.x != P3.x && P1.y != P3.y)
check P3 = P1 + (P2-P1) + (P4-P1)
if (P1.x != P2.x && P1.y != P2.y)
check P2 = P1 + (P3-P1) + (P4-P1)
if (P1.x != P4.x && P1.y != P4.y)
check P4 = P1 + (P2-P1) + (P3-P1)
otherwise return false
Wouldn't it be simpler to do the following:
Check if points 0, 1 and 2 span a triangle (if they are co-linear the quadrilateral is degenerate too)
Check the following segments for intersection:
[0, 3] and [1, 2]
[1, 3] and [0, 2]
[2, 3] and [0, 1]
If none of them intersects, the quadrilateral is non-convex.
Otherwise, you should have exactly one intersecting case. You found your opposite vertices there.
I have an array of "lines" each defined by 2 points. I am working with only the line segments lying between those points. I need to search lines that could continue one another (relative to some angle) and lie on the same line (with some offset)
I mean I had something like 3 lines
I solved some mathematical problem (formulation of which is my question) and got understanding that there are lines that could be called relatively one line (with some angle K and offset J)
And of course by math formulation I meant some kind of math formula like
Sort all your segments based on angle (in range 0 to Pi), and build a sorted map of angle-to-segment.
Decide on some angle difference threshold below which two segments can be considered parallel. Iterate through your map and for each mapping, consider adjacent mappings on either side of the angle (needs wrap around) which are considered parallel.
Within each set of nearly-parallel segments, see if they are "continuations" of each other.
Two segments (A,B) and (C,D) are roughly collinear if all possible pairings of the 4 points are roughly parallel. You can use the same test as above.
Pseudo-code:
Angle(A,B)
return Atan((B.y-A.y) / (B.x-A.x)) // use atan2 if possible, but needs wrapping
NearlyParallel(angle1, angle2)
delta = Abs(angle1-angle2)
return (delta < threshold) or (delta > Pi-threshold)
Collinear(A,B, C,D)
// Assume NearlyParallel(Angle(A,B), Angle(C,D)) == true
return NearlyParallel(Angle(A,C), Angle(B,D)) and NearlyParallel(Angle(A,D), Angle(B,C))
What have you tried so far?
I guess one way is to look for pairs of lines where:
the directions are similar |theta_1 - theta_2| < eps for some eps
there is at least one pair of end points where the points are close
Depending on the size of your problem you may be able to just check all pairs of lines for the conditions
Starting with: A = a2 – a1 where a1 and a2 are the angle of the two lines.
We can do this:
tan(A) = tan(a1 – a2) = (tan(a2) – tan(a1)) / (1 + tan(a1) tan(a2))
If tan(a2) is bigger than tan(a1) then tan(A) will be the acute angle between the two lines. You can then check tan(A) against your tolerance.
So I guess the formula would be:
tan(A) = (tan(a2) – tan(a1)) / (1 + tan(a1) tan(a2)) when tan(a2) > tan(a1)
But I'm no mathematician