|
I found this routine sometimes produces incorrect tesselation. When certain points in the polygon winding are colinear, it can skip over them and produce triangles outside of the original polygon.
My fix simply classifies points on the edge of the test triangle as 'inside' instead of 'outside'. This appears to be a stable and beneficial fix so far.
Example Input:
<br />
poly normal (0.000f,-0.500f,0.866f)<br />
poly points<br />
(0.700f,6.500f,4.071f)
(0.700f,5.500f,3.494f)
(-1.700f,5.500f,3.494f)
(-1.700f,6.500f,4.071f)
(-2.500f,6.500f,4.071f)
(-7.300f,1.700f,1.300f)
(11.300f,1.700f,1.300f)
(7.500f,5.500f,3.494f)
(4.300f,5.500f,3.494f)
(4.300f,6.500f,4.071f)
Old Code in function IsPointInside():
<br />
pmq2.Vector(m_e1,ntmp); if( (B0=m_N.Dot(ntmp)) <= 0.0 ) return false;<br />
m_e0.Vector(pmq2,ntmp); if( (B1=m_N.Dot(ntmp)) <= 0.0 ) return false;<br />
return ( (m_A-B0-B1) > 0.0 ? true : false );<br />
New Code:
<br />
pmq2.Vector(m_e1,ntmp); if( (B0=m_N.Dot(ntmp)) < 0.0 ) return false;<br />
m_e0.Vector(pmq2,ntmp); if( (B1=m_N.Dot(ntmp)) < 0.0 ) return false;<br />
return ( (m_A-B0-B1) < 0.0 ? false : true);<br />
Helpful explaination of internal variables:
<br />
Edit....
Further testing shows that neither the old or new code correctly and consistently handles coincident and colinear points. (Eg. An example of a coincident point, is a Figure '8' polygons that does not actually cross over, just converges in the middle.)
Example Input:
<br />
poly normal (0.000f,-0.500f,0.866f)<br />
poly points<br />
(1.700f,6.300f,2.801f)<br />
(4.300f,3.700f,1.300f)<br />
(5.700f,3.700f,1.300f)<br />
(7.000f,5.000f,2.051f)<br />
(8.300f,3.700f,1.300f)<br />
(9.300f,3.700f,1.300f)<br />
(8.000f,5.000f,2.051f)<br />
(7.000f,5.000f,2.051f)<br />
(1.700f,10.300f,5.111f)<br />
This should help more with coincident points:
Old code in IsAnyPointInside():
<br />
if( ( ip < i || ip > k ) &&<br />
IsPointInside(points[m_nIndex[ip]],points[ik]) )<br />
{<br />
return true;<br />
}<br />
New code:
<br />
if( ip < i || ip > k )<br />
{<br />
if( points[m_nIndex[ip]].Equal(points[m_nIndex[i]]) <br />
|| points[m_nIndex[ip]].Equal(points[m_nIndex[j]])<br />
|| points[m_nIndex[ip]].Equal(points[m_nIndex[k]])<br />
)<br />
{<br />
continue;
}<br />
if( IsPointInside(points[m_nIndex[ip]],points[ik]) )<br />
{<br />
return true;<br />
}<br />
}<br />
If topus's suggested change is valid, it should read
<br />
if( ((-FLT_EPSILON) < m_A && m_A < FLT_EPSILON) ||
((-FLT_EPSILON) < m_N[0] && m_N[0] < FLT_EPSILON &&<br />
(-FLT_EPSILON) < m_N[1] && m_N[1] < FLT_EPSILON &&<br />
(-FLT_EPSILON) < m_N[2] && m_N[2] < FLT_EPSILON ))<br />
return degenerate;<br />
Also note that you can help the algorithm by removing 'junk' before processing. Eg. project 3D points onto plane (of which normal is used). Remove degeneracies like coincident and colinear points. Snapping and merging points may help, but could lead to bad-snaps that cause self intersections.
Edit:
I have found another case which I believe shows a flaw in the algorithm. As 'ears' are clipped off the polygon, the remaining polygon can become self intersecting and fail 'point in poly' test, to produce triangles outside the original shape.
Example input:
<br />
poly normal(0,0,1)<br />
(10.52881622f,-1.25890017f,0.00000000f)<br />
(10.52881241f,0.24110639f,0.00000000f)<br />
(-1.47119045f,0.24110317f,0.00000000f)<br />
(-1.47119141f,-1.25889719f,0.00000000f)<br />
(4.52881575f,-1.25890088f,0.00000000f)<br />
(4.52881479f,-3.25889254f,0.00000000f)<br />
(6.08583260f,-3.25889111f,0.00000000f)<br />
(6.08583069f,-1.25889945f,0.00000000f)<br />
The fix:
in function Triangulate()
in 'case convex :'
in else, after 'RemoveVertex'
<br />
i = j;<br />
j = k;<br />
k++;<br />
This allows the point AFTER the removed triangle point to be the start of the next test triangle. This helps with near colinear points that fan out from an otherwise stationary start point.
Edit:
There are still issues with the code. The bottom line is that the original algorithm does not handle simple non-covex polys with colinear, but non intersecting segments. The overall algorithm does not handle polygons that intersect in anyway, including at single points, though it often produces a correct or near correct result.
Please comment on this.
|
|
|
|
|
In my crossplatform c++ framework ( www.amanith.org ) i've implemented a fast sweepline tesselator that can handle non simple polygons with holes , and some common degenerations.
For every questions abuot this, email me at: mfabbri@amanith.org
|
|
|
|
|
You had implemented it?
My god! Your code is too similiar to SGI/libtess.
Such functions as "sweepevent", "TessellateMonotoneRegion" ..., are almost the same to SGI/libtess.
I suggest you add " Thanks to SGI/libtess " in the header.
Hey, kavenger,
you should not always call "AddContourPoint",
If you had research the struct of Mesh carefully,you can add all your vertex once together. This can make you function 100% faster.
============================
Gods know that I really love you from the bottom of my heart,but I forget to tell you that...
|
|
|
|
|
Hey Liu,
I have read all libtess sources/docs, and got inspired during the writing of my tessellator, as weel as for another good paper, that explain the sweepline algorithm in details ( http://www.cs.ucsb.edu/~suri/cs235/Triangulation.pdf ).So, yes, i'll follow your suggestion adding the greetings to SGI.
My decision to rewite a tesselator instead of using the libtess ones is due to the fact that i had a try to be more robust and support more degeneration cases, without to introduce overheads of any kind.
Anyway, now, i'm oriented in a direction to use OpenGL drawing tecniques that don't need of the tesselation step, beacuse this step is too expensive, and it needs a too high robustness level.
Just for curiosity,
Are you using Amanith? Do u use the tessellation for drawing purposes or for something other?
( my mail is: mfabbri ]at[ amanith ]dot[ org )
|
|
|
|
|
Change the line:
for( register int i=0 , j=1 ; j < nPoints ; i++ , j++ ){
to:
for( register int i=0 , j=1 ; i < nPoints ; i++ , j++, j=j%nPoints ){
and normal of the polygon will be calculated correctly.
Torsten
|
|
|
|
|
It could be better for performance issue to exclude the case of last j from the for loop, in order not to compute the modulo each time.
I would do :
normal[0]=normal[1]=normal[2]=0.0f;
for( register int i=0 , j=1 ; j < nPoints ; i++ , j++ ){
normal[0]+= ( points[i][1] - points[j][1] ) * ( points[i][2] + points[j][2] ) ;
normal[1]+= ( points[i][2] - points[j][2] ) * ( points[i][0] + points[j][0] ) ;
normal[2]+= ( points[i][0] - points[j][0] ) * ( points[i][1] + points[j][1] ) ;
}
// last case :
normal[0]+= ( points[i][1] - points[0][1] ) * ( points[i][2] + points[0][2] ) ;
normal[1]+= ( points[i][2] - points[0][2] ) * ( points[i][0] + points[0][0] ) ;
normal[2]+= ( points[i][0] - points[0][0] ) * ( points[i][1] + points[0][1] ) ;
--------------------------------------------------
It would also be a good idea to delete Vector3F array in CPolyTryView::OnDraw(CDC* pDC) after call to triangulate() to avoid memory leaks !
|
|
|
|
|
Area computation is bugged :
you should add in CPolyTriView::OnDraw()
existing :
//
for( int i=0 ; i < m_nPoints ; i++ ){
points[i][0] = m_points[i].x;
points[i][1] = m_points[i].y;
if( i ) Area+= ( m_points[i].x * m_points[i-1].y - m_points[i-1].x * m_points[i].y) * 0.5f;
}
//
// added after for loop :
Area += ( m_points[0].x * m_points[i-1].y - m_points[i-1].x * m_points[0].y) * 0.5f;
Also, if area computation is only used for normal deduction, you can avoid *0.5f as it will not change area sign.
|
|
|
|
|
Thanks for the code.
Martin
|
|
|
|
|
Thank you very much for this example!
It's the simple and best tesselation
algorithm I found over the internet!
I found a special case that pop-up an
index range error (very hard to find!!! )
in the Triangulate function.
The "k" index missed 2 hits because it
starts at k=2... So I did a little patch!
New loop:
for( int i=0 , j=1 , k=2 ; k < nVertex+3 ; )
New indexes:
int ib = i % nVertex;<br />
int jb = j % nVertex;<br />
int kb = k % nVertex;
Here is the modified function:
int nTriangle= 0;<br />
int nVertex = nCount;<br />
AllocIndex(nCount);<br />
bool bNoErrors = true;<br />
while( nVertex > 3 && bNoErrors )<br />
{<br />
bNoErrors = false;<br />
for( int i=0 , j=1 , k=2 ; k < nVertex+3 ; )<br />
{<br />
if( nVertex == 0 )<br />
break;<br />
<br />
int ib = i % nVertex;<br />
int jb = j % nVertex;<br />
int kb = k % nVertex;<br />
<br />
switch( TriangleArea(points, <br />
m_nIndex[ib],<br />
m_nIndex[jb],<br />
m_nIndex[kb],<br />
normal) )<br />
{<br />
case convex :<br />
if( IsAnyPointInside(points,ib,jb,kb,nVertex) ){<br />
i = j;<br />
j = k;<br />
k++;<br />
}<br />
else<br />
{<br />
nTriangle++;<br />
AddFace(points,m_nIndex[ib],m_nIndex[jb],m_nIndex[kb]);<br />
nVertex = RemoveVertex(jb, nVertex);<br />
bNoErrors= true;<br />
}<br />
break;<br />
case concave :<br />
i = j;<br />
j = k;<br />
k++;<br />
break;<br />
case degenerate :<br />
nVertex = RemoveVertex(jb, nVertex);<br />
bNoErrors= true;<br />
break;<br />
}<br />
}<br />
}<br />
return nTriangle;<br />
<br />
TruePyroman
|
|
|
|
|
Another litle improvement:
Use previus code and change the line
while( nVertex > 3 && bNoErrors )
with the line
while( nVertex >= 3 && bNoErrors )
Also to solve the problem with degenerate points (alligned or sub-alligned) replace the test in function Triangle Area (that work fine for planar 2D polygon's)
//
// j is alligned from i to k ?
//
if( (-FLT_EPSILON) < m_A && m_A < FLT_EPSILON )
return degenerate;
with:
//
// j is alligned from i to k ?
//
if( (-FLT_EPSILON) < m_A && m_A < FLT_EPSILON &&
(-FLT_EPSILON) < m_N[0] && m_N[0] < FLT_EPSILON &&
(-FLT_EPSILON) < m_N[1] && m_N[1] < FLT_EPSILON &&
(-FLT_EPSILON) < m_N[2] && m_N[2] < FLT_EPSILON )
return degenerate;
Last (but not least) if some consecutive triangles as the same origin vertex you can optimizing opengl drawing code collecting the common side (from two consecutive triangles) and using triangle fan (example triangle vertex 0,1,2 && 0,2,3 etc.. can be collected in a triangle fan 0,1,2,3 etc..)
Thanks very much for this very speedy code
|
|
|
|
|
Hi ,
Ok Delauney rules in good tesselation. But in the case of non-convex poly you have to use the constrained form of the algorithm (DCT). You can achieve good tesselation without get rid of your algorithm :
as a post - process phase you can "mimimize" the edge size of convex quads. It's like use the turn edge tools in 3ds max. In this way, long triangles are converted in more equilaterals ones.
If i got time i'll try and send it to you.
|
|
|
|
|
A more little explanation about my article. In order to tesselate the polygon i use this tecnique:
First compute the normal of polygon (i use the newell method see Graphic Gems..)
For each sequence of three vertex ( i, j, k ) in the polygon compute the vector area (alias normal of triangle).
If the normal go in the opposite direction of polygon normal the triangle is 'concave' and go ahead.
If the area il 0.0 the point j is alligned from i to k and eliminated from the list of vertex.
If the normal of triangle is in the same direction of polygon normal the triangle is convex. In the last case check if any other point is inside the triangle (the IsPointInside function). This function test if the point is on solid angle ik ^ jk. if this is true compute distance of intersection k->point to line ij and test distance. If all is good emit triangle, remove vertex j from list and go to the next triplet....
The reason for wich i posted this article is that i don't find this tecnique in any book or article and ave a doubt about the mathematical basic of my algoritm In other words is true that it work in any case ? (my experience ask yes but is very far from a theorem...)
The Delauney algortim work fine but is 'computational' more expensive than my algortim (using my Delauney implementation) for this reason i try this way. At the end my apologize for my poor english but my sincerly idea is to contribute to codeproject and suggest a way to resolv a problem that i found. Thanks pepito.
|
|
|
|
|
(excuse me for my poor english too ) )
I didn't want you to use a delaunay algorithm. My message intention was to show another way than Delaunay to improve you algorithm. The one i describe is only an idea to turn edges to have better result. And i don't take it from any book too and i don't think it's computationnal intensive. I think this idea can be plug as a post process of you algorithm.
I respect you intention to contribute. And my idea is to contribute to the improvment of your. This was my only intention.
Thanks
Jeckle
|
|
|
|
|
This is a good start towards providing more information in your article. If you can provide a bit more detail then you should consider updating it.
|
|
|
|
|
I would suggest using a delauney triangulation - it will cut down the number of thin strips, and produce a much better tesselation.
Here's a link..
or do a search on google.
|
|
|
|
|
It appeared as though the Delauney method only worked for polygons that were contained within the convex hull of the set of polygons. It appears as the method shown here works on concave polygons as well.
|
|
|
|
|
A more little explanation about my article. In order to tesselate the polygon i use this tecnique:
First compute the normal of polygon (i use the newell method see Graphic Gems..)
For each sequence of three vertex ( i, j, k ) in the polygon compute the vector area (alias normal of triangle).
If the normal go in the opposite direction of polygon normal the triangle is 'concave' and go ahead.
If the area il 0.0 the point j is alligned from i to k and eliminated from the list of vertex.
If the normal of triangle is in the same direction of polygon normal the triangle is convex. In the last case check if any other point is inside the triangle (the IsPointInside function). This function test if the point is on solid angle ik ^ jk. if this is true compute distance of intersection k->point to line ij and test distance. If all is good emit triangle, remove vertex j from list and go to the next triplet....
The reason for wich i posted this article is that i don't find this tecnique in any book or article and ave a doubt about the mathematical basic of my algoritm In other words is true that it work in any case ? (my experience ask yes but is very far from a theorem...)
The Delauney algortim work fine but is 'computational' more expensive than my algortim (using my Delauney implementation) for this reason i try this way. At the end my apologize fro my poor english but my sincerly idea is to contribute to codeproject and suggest a way to resolv a problem that i found. Thanks pepito.
|
|
|
|
|
Pepito Sbazzeguti... "Gratta il Pepito, viene fuori il Peppone!"
Certo che avresti potuto essere più originale con lo pseudonimo.
Samantha Fox
|
|
|
|
|
Peppone for President!
Don Camillo
|
|
|
|
|
thank you guys ! I am only a senator but in the future, with some TV network...-D
|
|
|
|
|
Ah, spiritoso, eh?
Forza Berlusca, non ti curar di lor ma guarda e passa!
Samantha Fox
|
|
|
|
|
I have a suggestion for you: Write up that article properly!
That is, why don't you put some explanation of the problem itself, the solution utilized by your code, the potential use of the code and some code examples of how to use it in another application etc. ?
|
|
|
|
|
Sorry, and my apologize, but my english is very poor.!!!! This is a basic class to use in alternative to tools like gluTesselate. The glu function is much flexible but in the (frequent) case in wich you ave a simple polygon this class let you to take a more control.
For instance:
dont insert new points in the polygon (and if you attach vertex property like color or vertex texture coordinate dont request any interpolation of this parameters)
dont use expensive time functions like square root etc. is fast like gluTesselate (try it)
work in 3D
Pepito
|
|
|
|
|
Perhaps because his English isn't too hot?
Who made you the article Gestapo around here?
|
|
|
|
|
Anonymous wrote:
Perhaps because his English isn't too hot?
Who made you the article Gestapo around here?
You must understand that it takes more than just good code to make a good article. An explanation of how and why is essential.
Even if English isn't your first language, there's no reason an author shouldn't write it first in his or her own native language and then ask for assistance (via the Collaboration/Testing forum or even the Lounge) in translating it into English before posting.
Of course, these days you can also do some limited translation via the web, which may be enough.
Remember that there's no need to worry about English in an article being perfect - CPians are pretty understanding of language barriers.
Andy Metcalfe - Sonardyne International Ltd
Trouble with resource IDs? Try the Resource ID Organiser Add-In for Visual C++
"I would be careful in separating your wierdness, a good quirky weirdness, from the disturbed wierdness of people who take pleasure from PVC sheep with fruit repositories."
- Paul Watson
|
|
|
|
|