Recent

Author Topic: POLYGONs: problems adding certain features  (Read 2897 times)

Boleeman

  • Sr. Member
  • ****
  • Posts: 404
Re: POLYGONs: problems adding certain features
« Reply #15 on: January 01, 2023, 03:51:27 am »
Not sure why but I only get 2 windows come up, but after compiling to Release they disappeared.

With Version 4, the polygon does not refresh to the center when the window is resized unless the radius or the number of edges is changed.

I ended up playing around with Version 3 and moving the controls to the side to add extra height. Version 3 seems to work better for me (with no faults except the debug screens which have been turned off).

 I am still trying to work out:

How to make the gray back to transparent by setting a checkbox

I WORKED OUT HOW TO TOGGLE THE RADIAL LINES. YIPPY. Felt like I how learnt some things.

Thanks to VisualLab for being a great tteacher.

With the Save to transparent, I will look at the green screen code that someone talked about on the board.

Spring in Europe Late Autumn /early Spring. Over here in Australia it gets quite HOT, with HIGH UV, so it is easy to get burn't badly. We say Slip,Slop, Slap (slip on a shirt, slop on the sunscreen and slap on a hat). Must be nice weather in Europe at the moment.

« Last Edit: January 01, 2023, 10:45:56 am by Boleeman »

circular

  • Hero Member
  • *****
  • Posts: 4196
    • Personal webpage
Re: POLYGONs: problems adding certain features
« Reply #16 on: January 01, 2023, 06:21:25 pm »
I suppose you didn't incorporate the changes I made in v4  :'(
Conscience is the debugger of the mind

VisualLab

  • Sr. Member
  • ****
  • Posts: 291
Re: POLYGONs: problems adding certain features
« Reply #17 on: January 01, 2023, 08:03:02 pm »
I suppose you didn't incorporate the changes I made in v4  :'(

Yes. But not only. In the main module of the program, after changing the size of the window, the size of the TPaintBox object changed. In the handler of its "OnResize" event, the sizes of the TBGRABitmap bitmap were changed:

Code: Pascal  [Select][+][-]
  1. FPolygonPicture.SetSize(PaintBoxPolygon.Width, PaintBoxPolygon.Height);

But the bitmap has no resize event, so there was no change in the "FInvalidate" flag in the TPolygon class either. I changed the visibility of the "Invalidate" method of the TPolygon class to public. Now the polygon is centered. I also included changes in the main module of the program. I also added support for simulating a transparent background. This is the simplest variant. It can definitely be done better. I am attaching the source code of the next version.

Thanks also go to Circular for improving the TPolygon class.

Spring in Europe Late Autumn /early Spring. Over here in Australia it gets quite HOT, with HIGH UV, so it is easy to get burn't badly. We say Slip,Slop, Slap (slip on a shirt, slop on the sunscreen and slap on a hat). Must be nice weather in Europe at the moment.

It's pretty good for this time of year (at least today, where I live). But lovers of winter are unlikely to be satisfied, because there is no snow and frost. Especially children, because they can't go sledging or skiing. On the other hand, it has some advantages if 200 kilometers east (and beyond) the same weather persists. I think everyone knows what it is about.

Boleeman

  • Sr. Member
  • ****
  • Posts: 404
Re: POLYGONs: problems adding certain features
« Reply #18 on: January 02, 2023, 12:40:27 am »
Hi VisualLab.

I was playing around with version 3 when you were working on Version 4. I got playing with how to change the GUI, just for a bit of practice and then you had uploaded Version 4). I realized by moving the controls to the side there would be more room to draw a larger polygon on the screen, due to the fact that the screen was rectangular. I thought about a scrolling canvas to make the polygon graphics bigger, and not being restricted by the size of the Polygon Program Window.


With version 4 I knew that the onResize event was not working but I was confused why? Thanks for explaining it. You have really good problem solving skills to get around these problems.

I had a go at doing the toggle with the radial lines. I initially could not find in the IDE check1.checked property and later I remembered how you said the code to constructing the polygon was in one unit and the line drawing/colouring was on the other. It is way different to VB6 (painters method) so that was really confusing me. Thanks for providing the comments in the code.

I thank you and Circular. Here in Australia it was HOT again yesterday (later in the day) and today it is VERY WINDY (due to evaporating hot air). Real bush fire weather. To think that on the other side of Earth is colder (and snowing in some areas)!

You mentioned:  I also added support for simulating a transparent background. This is the simplest variant. For me simple steps are good. Then work on more confusing steps.

Take care and thanks for coming to the rescue again. I appreciate for kind help and guidance. Learnt heaps. I see the potential of Lazarus and Delphi to make really good graphics programs, only the method is different to the Painters Method that I was used to in VB6.
« Last Edit: January 02, 2023, 12:51:59 am by Boleeman »

Boleeman

  • Sr. Member
  • ****
  • Posts: 404
Re: POLYGONs: problems adding certain features
« Reply #19 on: January 02, 2023, 02:16:01 am »
Just played around with version 5 and I am really impressed. I see how your naming convention is much better than mine (so that is something I need to work on). The other day I spent time playing with things until eventually the checkbox worked. I didn't understand all the constructor and destructor code but I guess that's an evolving process. I now see that TPoints hold integer coordinates of points (I looked at someone else's code and saw that used different types for the array they created but they were storing points so that is what confused me).

Your alignment skills for the controls is also really good. I was selecting each side scrollbox while pressing shift and then right click to align left or right. Must play around with sizing a bit more.

I still cannot see what BGRABitmap code made the back transparent (except for alpha fill of text).

Can't get over how good the Polygon program has become. WoW. Thanks once again VisualLab.

circular

  • Hero Member
  • *****
  • Posts: 4196
    • Personal webpage
Re: POLYGONs: problems adding certain features
« Reply #20 on: January 02, 2023, 07:33:13 am »
But the bitmap has no resize event, so there was no change in the "FInvalidate" flag in the TPolygon class either. I changed the visibility of the "Invalidate" method of the TPolygon class to public. Now the polygon is centered. I also included changes in the main module of the program. I also added support for simulating a transparent background. This is the simplest variant. It can definitely be done better. I am attaching the source code of the next version.
Indeed, resizing was also something to handle.

Quote
Thanks also go to Circular for improving the TPolygon class.
Just a tiny contribution  :)

I still cannot see what BGRABitmap code made the back transparent (except for alpha fill of text).

That's because of the boolean parameter at the end of the line:
Code: Pascal  [Select][+][-]
  1. FPolygonPicture.Draw(PaintBoxPolygon.Canvas, 0, 0, False);

As a sidenote, there is a FillTransparent method that you can use to clear the BGRABitmap with transparent color. The difference with AlphaFill(0) is that the former will also clear RGB channels whereas the latter only sets the alpha channel. In most cases, that won't make a difference.
« Last Edit: January 02, 2023, 08:30:19 am by circular »
Conscience is the debugger of the mind

circular

  • Hero Member
  • *****
  • Posts: 4196
    • Personal webpage
Re: POLYGONs: problems adding certain features
« Reply #21 on: January 02, 2023, 07:57:28 am »
Here in Australia it was HOT again yesterday (later in the day) and today it is VERY WINDY (due to evaporating hot air). Real bush fire weather. To think that on the other side of Earth is colder (and snowing in some areas)!
Australia has a hot weather in general and in summer in particular. I understand that you dream about cold weather. Though when it is very cold here it is also difficult, in particular when you don't have enough heating. It would be better to have a temperature between 20 and 30 degrees all year long and everywhere on the globe  :)

I find it also difficult to wrap my head around the fact that the weather changes depending where we are, including seasons and time of day. In Australia, it is also night when it is day in Europe and vice versa.

In South Africa, it is the same time of day as in Europe, but it is summer now in South Africa like in Australia.
Conscience is the debugger of the mind

circular

  • Hero Member
  • *****
  • Posts: 4196
    • Personal webpage
Re: POLYGONs: problems adding certain features
« Reply #22 on: January 02, 2023, 08:28:31 am »
What is the difference between a TPoint and a Point specified by a single? In Vb6 I could specify points as integers and as single for differing situations.
Indeed, in the TPoint record, you can only store integer coordinates. It is faster than TPointF which uses floating points, but also more limited. In BGRABitmap, TPoint/TRect is used for drawing functions that are strictly aligned to pixels, whereas most fonctions use TPointF and TRectF to allow for sub-pixel coordinates: if you draw a pixel at coordinate (0.5, 0.5) it will be blurred among the surrounding pixels.

More info on pixel coordinates:
https://wiki.freepascal.org/BGRABitmap_tutorial_13

Quote
Also where do you find the syntax for different commands like say PolygonPicture.EllipseAntialias?
I was just blindly typing in the IDE after typing PolygonPicture. and paying attention to the intellisense scroll box, but there must be an easier way?
You can Ctrl-Click the fonction to get to the definitions and from there browse the source code. The name of the functions are quite explicit so that using the Intellisense will get you what you're looking for most of the time.

Unfortunately the wiki help is limited, any help making it is welcome, but if you don't find something you can ask on the forum and you'll get a quick reply.
Conscience is the debugger of the mind

VisualLab

  • Sr. Member
  • ****
  • Posts: 291
Re: POLYGONs: problems adding certain features
« Reply #23 on: January 02, 2023, 06:04:30 pm »
I didn't understand all the constructor and destructor code but I guess that's an evolving process.

I will try to present it using some simplifications. This is a very short explanation related to classes.



1. A class is a bit like a record. However, it differs from the record in that:

  • you can put functions and procedures in it, which are called methods,
  • has the ability to hide some fields (as in records, but not accessible from the outside)
  • it is not enough to declare a variable of a given class (ie: it is not created statically).

In Object Pascal, class names (but not only) are preceded by the letter T (type). This is not enforced by the compiler. It's a convention that makes life easier. Most languages do not use such prefixes. For example, it is rarely used in the C++ world, but the creators of the Qt library considered it a good practice - their class names are preceded by the letter Q.

In Object Pascal, class fields are usually preceded by the letter F (field). As before, this is not enforced by the compiler and is just a convention to make life easier.

Hiding fields and methods inside a class is similar to what constructors do with various devices. For example, integrated circuits, engines (electric, internal combustion), watches, etc. have a housing that does not allow you to rummage inside them. Thanks to this, it's harder to break something (it's still possible, but you have to try harder :) We can only use what the creator intended to use. On the other hand, thanks to this we do not have to go into details. At least that's how it is at first. After that, most people want to know how the object works. Either way, real-world engineering practice shows that this is a good approach*. This is called hermetization or encapsulation.

So classes are a schema of some object. It can be a representation of a real object (e.g. a button) or it can be completely abstract (e.g. a mathematical one like a matrix or a polynomial).



2. Because the creator of a class can put any fields inside it, some of them can be created dynamically at run time, such as dynamic arrays or objects (i.e. objects can consist of other objects). This is the case in the TPolygon class, which stores the coordinates of the polygon's vertices in an internal array. Depending on the number of vertices, the number of elements in this array changes, so a dynamic array is used. Also, sometimes it is the case that in classes some fields should be nulled or initialized with non-zero values. It all depends on what the developer is creating. Sometimes, certain values should not appear inside variables, because they can cause the program to malfunction or crash completely.

Therefore, special methods were created for classes: constructor and destructor. For example, a constructor can be used to:

  • allocating memory for dynamic fields (e.g. dynamic arrays) or setting them as empty (nil),
  • creating objects, if the class contains such objects,
  • initializing fields inside the class,
  • initiate network connections or database connections, - opening a file stream.

Constructors in Object Pascal have a name: Create, which is preceded by the keyword constructor. Of course, you can use a different name for the constructor, because it is preceded by a keyword anyway. But the name Create is easy to understand. You can create any number of constructors that have the same name (i.e. Create), but must differ in the number and/or type of arguments. This is in the TPolygon class which has 2 constructors:

Code: Pascal  [Select][+][-]
  1. constructor Create; overload;
  2. constructor Create(const AAngle, ARadius: Single; const AEdgeCount: Integer;
  3.                    const ABackColor, AFillColor, ALineColor: TColor;
  4.                    const ALineWidth: Integer; ABitmap: TBGRABitmap;
  5.                    const ARadialLines, ATrBack: Boolean); overload;

In general, in Object Pascal you can create multiple functions/procedures/methods with the same name. This is called overloading. They just need to differ in the number and type of arguments. For this to happen, you need to tell the compiler that you're overloading, that it's not an accidental error in your code. Overloaded methods are marked with the overload keyword after the method header.

The destructor does the opposite, i.e. it cleans up after an object that is no longer needed. For a class, a destructor is created when (by example):

  • you need to free the memory allocated for the class field in the constructor or some other method of this class,
  • termination of network connections or database connections,
  • closing the file stream,
  • sending a signal to other objects that the object is being removed.

There can only be one destructor in a class. Its purpose is only to clean up the remains of the facility. If there were several destructors, it would not be known in what situations each of them should be called. When cleaning, the situation must be unambiguous. This is in the TPolygon class which has only 1 destructor:

Code: Pascal  [Select][+][-]
  1. destructor Destroy; override;

This destructor replaces the destructor version of TPolygon's ancestor: TObject. Hence, after its declaration, the word "override" is entered. In TObject, the destructor is declared as:

Code: Pascal  [Select][+][-]
  1. destructor Destroy; virtual;

The virtual keyword can be used with any class method, not just the destructor. It informs that:

  • such a method appears for the first time in a given ancestor class,
  • classes of descendants of such a class can replace its content with other instructions.

Therefore, in descendant classes, inherited methods (with the names of the ancestor) must be marked with the word override.



3. To create an object that is of a specific class (e.g. TButton, TPolygon, etc.), you need to:

  • declare a variable of this class,
  • you need to call the constructor of the class.

That is: a class is a scheme according to which an object is created, i.e. an instance of a class.

When we want to create the source code for a class, we don't have to design it from scratch. You can build on the existing classes, adding what you need. This is called inheritance. In Object Pascal, all classes must share a common ancestor, TObject. This class deals with low-level details. This saves you from having to reinvent the wheel. This is why TPolygon is a descendant of TObject. This can be seen in the declaration of the TPolygon class, which is a descendant of the TObject class:

Code: Pascal  [Select][+][-]
  1. TPolygonData = class(TObject)



4. There are 3 levels of access to internal elements in class:

  • private - such fields and methods are used only by other methods of a given class, there is no access to them from outside the class, descendants of such a class cannot use them either,
  • protected: such fields and methods are used by methods of a given class and descendants of this class, there is no access to them from outside the class,
  • public - such fields and methods can be freely used outside the class.

A little digression about classes is in order here. In other languages, such as C++, classes can inherit from multiple ancestors (and then chimeras are created :) This has some advantages but also disadvantages.

In Object Pascal language, there is only inheritance from one class: TObject (this is also the case in C# and Java, for example). In addition, in the Object Pascal language, the following may occur in classes:

  • properties,
  • events.



5. Properties are usually implemented in the public part, but also in the protected part (in classes that are only used as ancestors that implement some common features for child classes). It doesn't make sense to put a property in a private part. For example, in the TPolygon class:

Code: Pascal  [Select][+][-]
  1. property Angle: Single read FAngle write SetAngle;

On the other hand, fields in the public part are practically not used, at least in Object Pascal. Properties are used instead. Properties can be:

  • read-only (keyword: read),
  • write-only (keyword: write),
  • read and write (keywords: read and write).

In practice, write-only properties are not used. Properties can read and write fields directly or using so-called access methods:

  • reading, usually has the prefix Get,
  • writing, usually prefixed with Set.

To make life easier, method names are formed using a prefix and property name. This is in the TPolygon class:

Code: Pascal  [Select][+][-]
  1. procedure SetAngle(Value: Single);

For example, in the TPolygon class, reading is done directly from the FAngle field. On the other hand, the recording is done through the access (saving) method: SetAngle. Write accessors are typically used to control what is inserted into a class field to avoid class malfunctions.

There may also be properties in the checkout that do not have corresponding fields in the private or protected part. Usually these are readable properties. For example, for a TPolygon class, you could (hypothetically) create an Area property like this:

Code: Pascal  [Select][+][-]
  1. TPolygonData = class(TObject)
  2. private
  3.   …
  4.   function GetArea: Single;  // calculates area of polygon
  5.   …
  6. public
  7.   …
  8.   property Area: Single read GetArea;
  9.   …
  10. end;
  11.  
  12.  
  13. function TPolygonData.GetArea: Single;
  14. begin
  15.   // instructions to calculate the area of the polygon
  16. end;

Of course, the TPolygon class doesn't need such a property. It was just an excuse to show the property without the corresponding field in the class.



6. Events are a way for other objects to be notified that something has changed (or happened, e.g. a click) in the current object. Events generally begin with the prefix On. They are declared similarly to properties, i.e. with the word: property. Events are actually pointers to methods in other classes.

Suppose (hypothetically) that we want to add an event to the TPolygon class informing that the coordinates of the polygon vertices have already been calculated. It could be done like this:

Code: Pascal  [Select][+][-]
  1. TPolygonUpdateEvent = procedure(Sender: TObject) of object; // a procedural type definition is a pointer to a method in an object
  2.  
  3. TPolygonData = class(TObject)
  4. private
  5.   …
  6.   FOnUpdated: TPolygonUpdateEvent;  // an event generated when the coordinates of polygon vertices have been recalculated
  7.   …
  8. public
  9.   …
  10.   property OnUpdated: TPolygonUpdateEvent read FOnUpdated write FOnUpdated; // a property that provides the class field that handles the event
  11.   …
  12. end;
  13.  
  14.  
  15. procedure TPolygonData.Prepare;
  16. var
  17. begin
  18.   …
  19.   // calculates the coordinates of the polygon's vertices
  20.   …
  21.   if  FOnUpdated <> nil
  22.    then FOnUpdated(Self);
  23. end;

In order for a class to react to events taking place in it, a field is defined inside this class, which must be of procedural type. Thanks to this, it can store a pointer to a procedure in some object (that is, it stores the address of the procedure located in some object). It is important that the procedural type refers to the method in the object. This is indicated by additional keywords: of object. It cannot be a pointer to an ordinary procedure or method (there may be other cases, but let's leave that for later).

In order to use such an event, you need to assign a method address from some object to the event. For the event defined above, it could be like this:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   … // previous instructions related to the program window
  4.   FPolygon.OnUpdate := @DoPolygonUpdate;  // remembering the procedure address from another object
  5. end;
  6.  
  7. procedure TForm1.DoPolygonUpdate(Sender: TObject);
  8. begin
  9.   LabelArea.Caption := FPolygon.Area.ToString;
  10. end;

In the code initializing the main window of the program, we substitute the DoPolygonUpdate procedure pointer to the OnUpdate event (@ is the operator that takes the address of the procedure). This procedure is implemented in the window class (TForm1). What you need to pay attention to are the arguments of the procedural type that is used to define the event and the arguments of the procedure whose address we substitute to the event. They must be identical in terms of the types of arguments and how they are passed. If in the procedural type TPolygonUpdatedEvent a keyword was used in them, e.g. const, and in the definition of the procedure DoPolygonUpdate it would not be used (or there would be a different word), then the compiler will report an error.

The given example declares its own type TPolygonUpdateEvent, which has only 1 argument of type TObject. In fact, you can use the library type TNotifyEvent (unit: Classes):

Code: Pascal  [Select][+][-]
  1. TNotifyEvent = procedure(Sender: TObject) of object;

In fact, the TPolygon class doesn't need this event. The example provided was to illustrate how to add an event to a class. A good introduction to the Object Pascal language may be the manual "Modern Object Pascal Introduction for Programmers" (HTML, PDF), written by Michalis Kamburelis.



*) There are people who think this is the wrong approach. According to them, we should go back to programming based on procedures and a huge set of loose variables. For some time there have been a lot of blogs in which various "specialists" (usually young) explain why, according to them, object-oriented programming is pure evil. However, the content of these descriptions indicates that these people do not understand why the object-oriented paradigm appeared, how to use it and what is gained from it. The most common examples are C++ or Java. The real problem, however, is related to the quirks of C++ (also an inheritance from C), or the limitations and verbosity of Java.

In fact, procedural programming is viable for small programs or when the computer is low on memory. In particular, this applies to programs created for microcontrollers (AVR, PIC, simpler ARM, etc.).
« Last Edit: January 02, 2023, 06:17:56 pm by VisualLab »

Boleeman

  • Sr. Member
  • ****
  • Posts: 404
Re: POLYGONs: problems adding certain features
« Reply #24 on: January 03, 2023, 02:16:25 am »
Thanks VisualLab and Circular for your extended replies (and and KodeZwerg in another thread). Much appreciated.

I have read your replies and have printed to pdf for further reading. Need to re read to fully understand the concepts discussed (to consolidate things better). I also will read the pdf book.

I now understand a bit more what overloading is about. When I was playing around with the code I came up with overload errors (like when trying to put text at a certaian location like paintbox.width / 2 ) I did not really understand why, only that something needed to be overloaded because I was dividing by 2.

Same with constructors and destructors. I knew objects were being created and destroyed, using read, write and set key words, but now after reading your explanations I am understanding a bit more.

With the transparent save feature I saw AlphaFill(0) but in other threads I saw in code something like:
            //AllCharsGrid:=TBGRABitmap.Create(512,512, BGRAPixelTransparent );
      SelectBmp:=TBGRABitmap.Create(64,64,BGRAPixelTransparent);

and

        Img.Transparent := True;
        Img.TransparentColor := clBlack;


I just could not see this in the polygon code. I see the Boolean set to False in to switch it on and off:
FPolygonPicture.Draw(PaintBoxPolygon.Canvas, 0, 0, False);

but where in the polygon code is the part that says make the canvas transparent? Still confused on this.

As always, many thanks to VisualLab and Circular and KodeZwerg for your advice and guidance. I had being trying to use Lazarus for a while but was finding it too difficult. I am getting better at it now, but still have a lot to learn.




« Last Edit: January 03, 2023, 10:22:19 pm by Boleeman »

VisualLab

  • Sr. Member
  • ****
  • Posts: 291
Re: POLYGONs: problems adding certain features
« Reply #25 on: January 03, 2023, 10:40:30 pm »
I now understand a bit more what overloading is about. When I was playing around with the code I came up with overload errors (like when trying to put text at a certaian location like paintbox.width / 2 ) I did not really understand why, only that something needed to be overloaded because I was dividing by 2.

Overloading has nothing to do with errors or division. Overloading is writing code for procedures or functions where several of them have the same name but differ in the number and types of arguments. For example:

Code: Pascal  [Select][+][-]
  1. function Add(A, B: Integer): Integer;
  2. function Add(A, B: Single): Single;
  3. function Add(Numbers: array of Integer): Integer;
  4.  
  5.  
  6. function Add(A, B: Integer): Integer;
  7. begin
  8.   Result := A + B;
  9. end;
  10.  
  11. function Add(A, B: Single): Single;
  12. begin
  13.   Result := A + B;
  14. end;
  15.  
  16. function Add(Numbers: array of Integer): Integer;
  17. var
  18.   X: Integer;
  19. begin
  20.   Result := 0;
  21.   for X in Numbers do
  22.    begin
  23.     Result := Result + X;
  24.    end;
  25. end;

The first two differ only in the types of arguments. The third one differs from the two above in the number and number of arguments (the first two functions take 2 arguments, the third function takes 1 argument, that is an array of numbers). Overloading can apply to ordinary functions or procedures, as well as methods in classes, including their constructors (destructors cannot be overloaded).

Same with constructors and destructors. I knew objects were being created and destroyed, using read, write and set key words, but now after reading your explanations I am understanding a bit more.

Constructors and destructors have nothing to do with the read, write, and set keywords. A constructor is a procedure placed in a class and preceded by the word constructor. A destructor is a procedure placed in a class and preceded by the word destructor. The read and write keywords are related to properties. They are placed when creating a property in a class. Set is not a keyword. This is a prefix used to naming procedures of access for field of class (write accesors) that bind a property to a class field. It is not checked by the compiler. But using names with Get and Set prefixes makes these procedures (accessors) more readable in code.

but where in the polygon code is the part that says make the canvas transparent? Still confused on this.

Transparency is a feature of the TBGRABitmap class. It is this class that creates graphics with a transparent background. The TPaintBox class only displays what is drawn on it. To force drawing a transparent background, the TrBack property has been added to the TPolygon class. Because this name is not very telling, in the next version of the project I changed its name throughout the program to: TransparentBackground in all places in the code, in both source files.
« Last Edit: January 03, 2023, 10:51:17 pm by VisualLab »

Boleeman

  • Sr. Member
  • ****
  • Posts: 404
Re: POLYGONs: problems adding certain features
« Reply #26 on: January 11, 2023, 12:58:25 am »
VisualLab, I have been painting at home, so please excuse the late reply.

Many thanks VisualLab for making the transparency part a bit more obvious to myself in the code. Also thanks for the link to the pdf book. Lastly thanks for all your gracious help in creating the polygon code for me to learn from.


 

TinyPortal © 2005-2018