Zoek

Uitgebreid zoeken Artikelen per auteur

  

Polygons in VO

Polygons in VO

This paper is about  polygons. We are going to display and move polygons. We are going to do a hit test as well and I will show you how easy it is to compute the area of the polygon.

To make you think about how you can use this knowledge, I’ll tell you how I got involved.

Over a year ago one of my customers came to me and asked me if I could write a database application with a graphical front-end. This front-end had to display ships on several different maps of a harbor. The ships differ in size, the maps differ in scale and the quays in the harbor are not only horizontal and vertical, but have all kinds of directions. And it should be possible to  drag and drop ships to their moor position. These were the challenges.

Draw a polygon

Let’s start drawing a polygon first. To do so, we use a Win32 API function Polygon.

This function needs a pointer to the device context, an array of winpoint structures and the number of points to be drawn.

So the first code looks like this:

METHOD onDrawPolygon( ) CLASS DiW_Demo1

  LOCAL hDevC                 AS PTR
  LOCAL DIM aVerticles[10]    IS _WinPoint
  aVerticles[1].x := 20
  aVerticles[1].y := 30
  aVerticles[2].x := 20
  aVerticles[2].y := 130
  aVerticles[3].x := 120
  aVerticles[3].y := 130
  aVerticles[4].x := 120
  aVerticles[4].y := 230
  aVerticles[5].x := 220
  aVerticles[5].y := 230
  aVerticles[6].x := 220
  aVerticles[6].y := 30

  hDevC :=  GetDC( SELF:Handle() )
  Polygon(hDevC, @aVerticles, 6 )
  ReleaseDC( SELF:Handle(), hDevc )     

  RETURN NIL

Listing 1: Our first polygon

We get the device context (and we do not forget to release it afterwards) and we call the Polygon function with the defined parameters. If we run this code we see a simple polygon drawn on a window.

That is nice, isn’t it. We can add some colors by adding these lines:

  dwColor   := RGB ( 45, 100, 0 )
  hBrush    := CreateSolidBrush( dwColor )
  hOldBrush := SelectObject( hDevc , hBrush  )

  // After painting our polygon we must restore
  // the previous brush and delete the brush
  SelectObject ( hDevc , hOldBrush )
  DeleteObject ( hBrush )

Listing 2: Adding color to our polygon

We can do more to make our drawing even more fancy, but I suggest you have a look at the draw methods of some of the draw classes inside VO. You will find a LineObject, RectangleObject, EllipseObject and more.

Now we know how to draw a polygon on a window, we write a class for it. After all, we are in an object oriented language. Our basic class will looks like this:

CLASS EWV_Polygon
  PROTECT dwColor          AS DWORD
  PROTECT aCurrentpos      AS ARRAY
  PROTECT hWindow          AS PTR
  PROTECT iID              AS INT

METHOD Axit CLASS EWV_Polygon
  IF !InCollect()
    SELF:Destroy()
  ENDIF
  RETURN NIL

METHOD Destroy() AS VOID PASCAL CLASS EWV_Polygon
  aCurrentpos := NULL_ARRAY  //hold the current pos.
  hWindow     := NULL_PTR
  RETURN

METHOD INIT( oSurfacePtr, id )  CLASS EWV_Polygon
  SELF:hWindow     := oSurfacePtr
  SELF:iId         := id
  SELF:aCurrentpos := {}
  SELF:dwColor     := RGB( 45,0,0 )
  SELF:dwRop       := R2_XORPEN
  RegisterAxit( SELF )
RETURN SELF  

ASSIGN Verticles( aPoints AS ARRAY );
  AS ARRAY PASCAL CLASS EWV_Polygon
  SELF:aCurrentpos := AClone( aPoints )
  RETURN SELF:aCurrentposodeline2;

Listing 3: Basic Polygon class

Lets go back to our drawing code. If you have a closer look, you will see a problem. The array is a dimmed array, so the dimension is hard coded. That is not very practical.

We could of course take a large size dimmed array, but if we need a great number of polygons, this will consume too much memory.

We need to dynamically create a dimmed array

What we need here is to be able to dynamically create a dimmed array. That sounds like a contradiction, but it is possible. Not that I knew how, but here I want to emphasize the use of the international newsgroup.  Most questions over there are answered within hours.

This is what my onDrawPolygon event should look like:

METHOD onDrawPolygon() CLASS Diw_Demo2
  LOCAL aCorners AS ARRAY
  LOCAL oPolyGon AS EWV_Polygon
  aCorners := { { 20,30 },;
    { 20,130 },;
    { 120,130 },;
    { 120,230 },;
    { 220,230 },;
    { 220,30 } }
  oPolyGon := EWV_Polygon{ SELF:Handle(), 5333 }
  oPolyGon:Verticles := aCorners
  oPolyGon:Draw( 10 )
  RETURN NIL 

Listing 4: How it should be

Now the aCorners array is a dynamic one. We still hardcode the values here, but we could  also have read them from a database.

The dynamical creating of the DIM-med array is done by the EWV_Polygon:Draw method.

We MemCalloc memory for our array and fill the array element by element:

  pVertices := MemCAlloc(wElem, _SizeOf( PolyVertices ))
  IF pVertices != NULL_PTR
    FOR n := 1 UPTO wElem
      structWinPoint := @pVertices.aVertex[n]
      aSubArray        := aCurrentPos[n]
      structWinPoint.x := aSubArray[1]
      structWinPoint.y := aSubArray[2]
    NEXT     
    Polygon(hDevC, @pVertices.aVertex, Integer(wElem))

Listing 5: Dynamic creation of Dimmed array

If we run this code, it will crash at runtime ... that is, if we have the range-check compiler option selected. So we need to deselect this and it will run fine.

We can add as many vertices as we like and our polygon class can handle it all.

A nice feature here is shown by pressing the draw button a second time.

This is done by using SetRop2( R2_XORPEN). The SetRop2 function sets the current foreground mix mode. GDI uses the foreground mix mode to combine pens and interiors of filled objects with the colours already on the screen. The foreground mix mode defines how colours from the brush or pen and the colours in the existing image are to be combined.

This is going to be very handy when we are going to move or rotate the object. Otherwise we had to repaint (at least part  of) the background first, A perhaps negative side effect is that our polygon is drawn with a transparent colour.

Now that we’ve reached our first milestone, we want more, don’t we?

Rotation

Next step is to rotate our polygon. To do so, we first need to calc the centre of the polygon. There are other ways probably, but what I do here is to find the smallest and the largest point on the x-axis, and divide them by two. I do the same with the largest and smallest point on the y-axis.

METHOD CalcPos() AS VOID PASCAL CLASS EWV_Polygon
  LOCAL wLen  AS DWORD
  LOCAL ixMin  AS INT
  LOCAL iyMin  AS INT
  LOCAL ixMax  AS INT
  LOCAL iyMax  AS INT
  LOCAL n      AS DWORD
  ixMin  := iyMin := ixMax := iyMax  := 0
  wLen := ALen( SELF:aCurrentpos )
  FOR n := 1 UPTO wLen
    ixMin := Min( aCurrentpos[n][1], ixMin )
    iyMin := Min( aCurrentPos[n][2], iyMin )
    ixMax := Max( aCurrentpos[n][1], ixMax )
    iyMax := Max( aCurrentPos[n][2], iyMax )
  NEXT
  SELF:ixPos := Integer( (ixMin+ixMax / 2 ) )
  SELF:iyPos := Integer( (iyMin+iYMax / 2 ) )
  SELF:aStatic := AClone( SELF:aCurrentpos )
  FOR n := 1 UPTO wLen
    aStatic[n][1] -= SELF:iXpos
    aStatic[n][2] -= SELF:iYpos
  NEXT
  RETURN

Listing 6: Calculate the centre of the polygon

In de code above the aStatic array is introduced. We use this as a base. This array holds the original size/shape of the polygon, where aCurrentpos holds the vertices of the displayed polygon that can be sized, moved and/or rotated.

The ixPos and the iyPos are what I call the position of the polygon.

With a SetAngle method we rotate our drawing:

  fSin := Sin(  fAngle*0.0174532925199432957695 )
  fCos := Cos(  fAngle*0.0174532925199432957695 )
  FOR n := 1 UPTO wElem
     fx := SELF:aStatic[n][1] * ;
           fCos + SELF:aStatic[n][2] * fSin
     fy := SELF:aStatic[n][1] * ;
           fSin - SELF:aStatic[n][2] * fCos
     SELF:aCurrentpos[n][1] := Integer( fX + SELF:ixPos)
     SELF:aCurrentpos[n][2] := Integer( fY + SELF:iyPos)
  NEXT
  RETURN

Listing 7: Rotating the polygon

As you can see, I am used to working in degrees instead of radials, so I have to do some conversion. Next is to compute the new positions of the vertices, where we need the Sin and Cos functions. This is not really my area. But thanks to the internet I have found some C-routines that only had to be translated into VO.

Let’s take a look at the demo now:

By pressing the buttons we are able to move and rotate the polygon. What does the moving code look like? Well, that is an easy one. After we have calculated the position of our object, we only have to move it to the new position

METHOD MoveTo(iNewX AS INT,iNewY AS INT) ;
  AS VOID PASCAL CLASS EWV_Polygon

  LOCAL wElem, n AS DWORD
  wElem := ALen( SELF:aCurrentpos )
  SELF:Draw()
  FOR n := 1 UPTO wElem
    aCurrentPos[n][1] := (aCurrentPos[n][1]-ixPos)+iNewx
    aCurrentPos[n][2] := (aCurrentPos[n][2]-iyPos)+iNewy
  NEXT
  SELF:Draw()
  SELF:ixPos := iNewx
  SELF:iyPos := iNewy
RETURN

Listing 8: Moving the polygon

Hit test

The next step is to perform a hit test. I have been searching through the Win32 SDK to find a function for this. And yes, it can be done. We need to create a so called region with CreatePolygonRgn() and then call PtInRegion() to determine if the point falls within the region. But this can be expensive in both GDI resources and in speed. And as a bonus,  if a polygon is complex, CreatePolygonRgn() will often fail due to lack of memory in Windows because regions are in the GDI's heap.

This is something Microsoft found out before I ran into trouble, so they provide an alternative (see MSDN Article 121960 ). This article contains a nice set of C-functions and I have translated these into VO as well. It is fast and does not use regions. The trick lies in determining the number of times an imaginary line drawn from the point you want to test crosses edges of your polygon. If the line crosses edges an even number of times, the point is outside the polygon. If it crosses an odd number of times it is inside. The line is drawn horizontally from the point to the right.

The original C-code is included as a textblock in the AEF. I will not go into the details of this code. What I will do is to show you how fast VO calculates a hit test with these routines. The next figure shows several, more or less complicated polygons and inside the mousemove-event a hit test is performed on the polygons.

Area

I am not very well educated on math’s. But I need to be able to calculate the area of the polygon. And of course I want a fast, optimized algorithm for it.  Thanks (again) to the internet, I did not have to do a lot of work myself; again I only had to translate a C-routine into VO.

I am not very well educated on math’s

In C it is almost a one-liner (how easy can things be?):

Float area2D_Polygon( int n, Point* V )
{
  float area = 0;
  int   i, j, k;     // indices

  for (i=1, j=2, k=0; i<=n; i++, j++, k++) {
     area += V[i].x * (V[j].y - V[k].y);
  }
  return area / 2.0;
}

Listing 9: C-Routine to compute the area of a 2D polygon

Let’s take a simple polygon to be able to understand this algorithm (see the figure):

A = 50, 100
B = 50, 200
C = 350,200
D = 350,100

Now this is what we need to do. We create an array of the points and add the first two points so we get {A,B,C,D,A,B}. The area*2 now is found with:

Bx * (Cy – Ay )  +  // -5000
Cx * (Dy – By )  +  // 35000
Dx *( Ay – Cy )  +  // 35000
Ax *( By – Dy )     // -5000

So the Area will be 60000/2.

To ‘prove’ this is a valid way to calc the area, I have made the polygon a little more complicated (see the figure). We would expect the same result, but let’s see what our algorithm makes of it.

Our array will look like {A,B,C, D, E,F,G,H,A,B}.

A =  50,100
B =  50,200
C = 250,200
D = 250,150
E = 350,150
F = 350,50 
G = 250,50
H = 250,100

And the computation goes like:

Bx * (Cy - Ay )+  //  50 * (200 - 100)=   5000                                                     
Cx * (Dy - By )+  // 250 * (150 - 200)= -12500                                                  
Dx *( Ey - Cy )+  // 250 * (150 - 200)= -12500
Ex *( Fy - Dy )+  // 350 * (50 - 150 )= -35000 
Fx *( Gy - Ey )+  // 350 * (50 - 150 )= -35000 
Gx *( Hy - Fy )+  // 250 * (100 - 50 )=  12500
Hx *( Ay - Gy )+  // 250 * (100 - 50 )=  12500   
Ax *( By - Hy )   //  50 * (200 - 100)=   5000  

Now we get a negative value, - 60000/2=-30000, so we’ll have to change it into a positive one.

The VO- code for the Polygon:Area access will be:

  w :=  ALen(SELF:aCurrentpos )
  aVertex := ArrayCreate( w + 2 )
  FOR i := 1 UPTO w
     aVertex[i] := acurrentpos[i]
  NEXT
  aVertex[w+1] :=  acurrentpos[1]
  aVertex[w+2] :=  acurrentpos[2]
  farea := 0.00
  FOR i = 2 UPTO w+1
    farea += aVertex[i,1] * ;
             (aVertex[i+1,2]-aVertex[i-1,2])
  NEXT
  IF fArea < 0
    fArea := -fArea
  ENDIF
  RETURN fArea/2.0

Listing 10: Area access of the poygon

Conclusion

You have seen how you can draw a polygon, with the aid of a dynamically created DIM-med array. We have moved our drawing around the screen, enlarged it and made it smaller. We have rotated it in any direction, performed hit tests and we have seen a way to compute the area. I am sure you can find a practical use for it and I hope you enjoyed reading.

De sources zijn beschikbaar via Visser_Polygons_Sources.zip.

If you have any questions, please don’t hesitate to contact me at evisser@wilg.nl

Commentaar van anderen:
bags op 9-7-2010 om 8:31
replica handbags,the Replica Handbags Online Store!The perfect Anya Hindmarch handbags are the ultimate accessory for any outfit Balenciaga handbags. As a leading provider of designer replica Bally handbags,Always leading the replica Bottega handbags of fashion without compromising traditional craftsmanship of replica croum leather goods, replica DeWitt is known by men and wemen.Great replica ebel. The cutest models. The Loewe handbags to all tastes and preferences replica Loewe handbags.Replica LV Monogram Vernis Coin Jimmy Choo replica handbags · Replica LV Monogram Vernis Jp Tods replica handbags. The Coin Purse Chapeau in Juicy Corture replica handbags.
replica handbag op 16-7-2010 om 9:23
When itdesigner handbags comes to forking over yourhermes replica handbags hard earned cash, you want to make surecartier replica handbags that you A)don't pay more thanMulberry replica you have to, but B) you don'tBreitling for sale pay for something that is notswiss watches worth its value. Pay attentionLongines replica to the details. Your handbag Maurice Lacroix replicashould carry the same Louis Vuitton replicaquality on the inside as itMaurice Lacroix replica does on the outside.fendi leather handbags If the outside material looks passablechristian dior handbags for the real thing, but the inside materialcheap burberry handbags looks less than perfect, you've gotMiu Miu replica a knockoff. Check that the hardware looks as high quality omega for saleas the rest of the purse. If it looksreplica handbags less cheap, doesn't functionswiss watches properly, carries a off odor or Cartier for saleisn't aligned correctly, it's more than likely a fake.ysl handbags Another tell tale sign is chopard for salethe stitching. Crooked, loose or poorCheap Handbags stitching means its not the real deal.
jkafjf jfklajfljfl op 28-7-2010 om 4:33
Tiffany jewelry manufacturers directory,cheap jewelry - lots of registered importers and exporters. Cheap tiffany jewelry manufacturers, Tiffany & co jewelry suppliers, tiffany jewelry wholesales, Tiffany Watches,exporters, sellers, traders and gold tiffany jewelry Distributors from China and around the world at www.goldtiffanyjewelry.com Tiffany jewelry exporters, Tiffany Accessories, Tiffany Bangles, Tiffany Bracelet, Discont Tiffany Bracelet, Discount Tiffany Cufflinks, Fashion Tiffany Earring, Tiffany Necklaces , Discount Tiffany Necklaces, Tiffany Rings, Tiffany Watches
Geef feedback:
Verzend Commentaar