First of all, pardon my ignorance.
I've searched long and hard to decipher these symbols, got to a point I thought to understand them but the implementation gives me unexpected results.
In a paper I found this function (excerpt from source):
intersect(P1,P2,Q1,Q2,alphaP,alphaQ)
WEC_P1 = <P1 - Q1 | (Q2 - Q1)⊥>
WEC_P2 = <P2 - Q1 | (Q2 - Q1)⊥>
if (WEC_P1*WEC_P2 <= 0)
WEC_Q1 = <Q1 - P1 | (P2 - P1)⊥>
WEC_Q2 = <Q2 - P1 | (P2 - P1)⊥>
if (WEC_Q1*WEC_Q2 <= 0)
alphaP = WEC_P1/(WEC_P1 - WEC_P2)
alphaQ = WEC_Q1/(WEC_Q1 - WEC_Q2)
return (1); exit
end if
end if
return (0)
end intersect
As a non-mathematician I didn't know what WEC stands for, but it's pretty easy to find out it stands for Window-Edge-Coordinate (GUESS 1). I didn't know what <u|v> means, wolfram-alpha says it is the dot product (GUESS 2):
<u|v> := u.x * v.x + u.y * v.y
I don't know what the upside-down T (⊥) is. An online search lead me to believe it is the perpendicular (\perp) symbol. The perpendicular vector is in my understanding the non-normalized normal vector, just the same but magnitude is not 1 (GUESS 3):
$$\begin{pmatrix} x \\\ y \end{pmatrix}⊥ = \begin{pmatrix} -y \\\ x \end{pmatrix}$$
I've already implemented a simple clipping algorithm where the determinant is computed up-front and checked to be greater than zero (to prevent a potential division by zero).
Now what I see in the above intersect() function is that the determinant is never computed. But as a computer scientist I understand that it is implicit, there's no way it gets actually thrown out of the window. And to avoid divisions by zero there are two if(), like this one (GUESS 4):
if (WEC_P1*WEC_P2 <= 0)
WEC_Q1 = <Q1 - P1 | (P2 - P1)⊥>
WEC_Q2 = <Q2 - P1 | (P2 - P1)⊥>
What I tried to do was take the simple clipping algorithm (which I understand thoroughly, computing determinant, alphas and using standard vector representations), GUESS 1-4 and implement the pseudo-code above in C#.
Through comparison (mutatis mutandis) I got the the following implementation:
public static bool AlphaClip(Vertex p1, Vertex p2, Vertex q1, Vertex q2, out float alphaP, out float alphaQ)
{
float WEC_P1, WEC_P2, WEC_Q1, WEC_Q2;
WEC_P1 = (p1.y - q1.y) * (q2.x - q1.x) - (p1.x - q1.x) * (q2.y - q1.y);
WEC_P2 = (p2.y - q1.y) * (q2.x - q1.x) - (p2.x - q1.x) * (q2.y - q1.y);
if (WEC_P1 * WEC_P2 <= 0f)
{
WEC_Q1 = (q1.y - p1.y) * (p2.x - p1.x) - (q1.x - p1.x) * (p2.y - p1.y);
WEC_Q2 = (q2.y - p1.y) * (p2.x - p1.x) - (q2.x - p1.x) * (p2.y - p1.y);
if (WEC_Q1 * WEC_Q2 <= 0f)
{
alphaP = WEC_P1 / (WEC_P1 - WEC_P2); // <--
alphaQ = WEC_Q1 / (WEC_Q1 - WEC_Q2);
return true;
}
}
alphaP = 0f; // implemetation artifact, can be ignored
alphaQ = 0f;
return false;
}
Through testing I know for a fact that it works, almost.
The problem is this: under circumstances (on edge cases) alphaP turns out to be NaN because WEC_P1 and WEC_P1 - WEC_p2 are zero (at the line with the <-- comment.
I thought the if (WEC_P1 * WEC_P2 <= 0f) would protected against this, being the equivalent of checking that the determinant is greater than zero.
I thought it may require a stricter condition (< instead of <=) but I don't really know what the WECs signify, they are too far removed from my vector/matrix comprehension, and reading about them doesn't help at all. I've found them only here (page 8, slides) in the Liang-Barsky algorithm, but these slides and the Liang-Barsky itself is insufficient to gain insight at the level required to debug the code.
My real suspicion is that I didn't understand correctly the meaning of the pseudo-code and my implementation is simply wrong. Something is missing. Maybe a sign is inverted or the WECs are computed wrong.
I'm shrugging my head over this for three days now, what is wrong?
Short answer: relevant excerpt from the paper
Right after that they describe a way to detect degeneracies, to which I'll add some amendment: their solution only handles three collinear points, but not four. Maybe that particular case is handled in some other way, but I don't feel like reading the whole thing. But as a standalone, that pseudo-code doesn't handle every cases.
The long version
I'm not familiar with the expression WEC, but guess 1 is reasonable, guess 2 and 3 are fine, guess 4 is wrong. I'm basing this judgement on the computation of these window edge coordinates.
A necessary, but not sufficient, condition for (line) segment $\overline{P_1P_2}$ to intersect segment $\overline{Q_1Q_2}$, is that segment $\overline{P_1P_2}$ must intersect the (infinite) line $(Q_1Q_2)$. To figure that out, you can pick one point of line $(Q_1Q_2)$, say $Q_1$, and a normal vector of the line, say $\vec n= (Q_2-Q_1)^\perp$. Then $\text{WEC}(P) = \langle P-Q_1|\vec n\rangle$.
For any point $P\in\mathbb R^2$, its WEC can be interpreted as how far "above" or "below" it is from line $(Q_1Q_2)$, based on the direction of $\vec n$. In particular you have \begin{align*} (Q_1Q_2) &= \left\{ P\in\mathbb R^2 : \langle P-Q_1| \vec n\rangle = 0 \right\} \\ \mathscr H_+(\vec n) &= \left\{ P\in\mathbb R^2 : \langle P-Q_1| \vec n\rangle > 0 \right\} \\ \mathscr H_-(\vec n) &= \left\{ P\in\mathbb R^2 : \langle P-Q_1| \vec n\rangle < 0 \right\} \end{align*} where $\mathscr H_+(\vec n)$ is the half-space above $(Q_1Q_2)$, and $\mathscr H_-(\vec n)$ is below.
With that said, the condition
WEC_P1*WEC_P2 <= 0is equivalent to eitherWEC_P1andWEC_P2are non-zero, orWEC_P1and/orWEC_P2is zero.If you change the test to a strict inequality, it will solve your NaN problem, but your code won't detect every type of segment intersection.
If
WEC_P1-WEC_P2is non-zero, this means line $(P_1P_2)$ is not parallel to $(Q_1Q_2)$, which in turn implies thatWEC_Q1-WEC_Q2is non-zero. In this case, you can safely computealphaPandalphaQwithout triggering NaN.If
WEC_P1-WEC_P2is zero, then the two lines are parallel and you either have:alphaP/Qis+/-inf,For the three last cases, you have
WEC_P1 = WEC_P2 = WEC_Q1 = WEC_Q2 = 0, andalphaP/Qis NaN. If those three cases are of no importance for you, you can just check for NaN (or test four equalities) and be done with it. Otherwise, you need to add a new conditional branch, and compute a new measure to distinguish the 3 cases. (Say, something like $\text{Not-Really-A-WEC} (M) = \langle M-P_1\mid P_2-P_1\rangle$.)