Screen shot showing the rectangular drawing object selected
Introduction
A lot of the OpenGL samples are oriented to display graphics, but since this project imitates the Windows paint application, there is more user interaction than in many of the other examples, and getting opengl object selection to work, for example, can be a pain. My intent was to learn OpenGL a little better, and the CsGL libraries for C# were very good, and the samples very helpful. Now it seems CsGL has been superseded by Tao, so this article is already out of date! I'm not an OpenGL guru by any means, so if you're a game developer, this article is below you, but if you're stuck on something like how to get the Bezier curves to work, then this might help you.
Using the code
I've been following some of the Randy Ridge examples from Sourceforge, and had set up a number of little Windows demo apps following his form/view pattern so that I made a little checklist that I go by each time:
Steps we're currently following to set up a Windows CS GL application:
- Begin a winforms app
- Add a project reference to winnt/system32/csgl.dll
- Add a class � myView, and inherit from OpenGLControl
- Add the required "#using" lines
- Copy the necessary subroutines from the dots (or this) sample
There may be a better way of displaying CS OpenGL in a Windows app, but I've been too lazy to try to improve on this once it seemed to work well. Any other ideas would be welcome.
Reference: I believe I got the GL-view from the dots demo example in the example set.
Also, it looks like having a reference to the dll will cause csgl.dll to be copied to the bin/release directory. At the moment, I don't know how to run the program if the dll is not in the directory with the exe.
Sets of sets of openGL objects : For what I want to do, it made sense to develop some drawing objects that would share some methods, but differ in their particular geometries - line, rectangle, ellipse, free form curve. But the DrawingObjects aren't pure geometric classes, or OpenGL classes; they also inherit from Windows.Forms
. Once a particular rectangle has center stage, for example, it seemed to make most sense for it to handle all the mouse events related to its top left and bottom right point being drawn. Once a drawing object is built, it is inserted into a hash table.
Speed/structure - I'm not a game developer, and I wonder if some games use collections of self-drawing objects all stored in hash tables, like this. I have some misgivings about this structure, because currently I'm iterating through all my drawing objects whether they have been modified or not, on each paint cycle. I have a feeling that this structure will become unwieldy after a hundred drawing objects, and I'm wondering about OpenGL drawing lists as a solution.
View-communicating-to-Form "wormhole" - I wanted to keep a clean boundary between the form class and the view class, but since I was imitating the windows paint program, and they display the current mouse position on the form, that threw me into a tizzy, because only the view knows where the mouse is, and the view is the child of the form. In "normal" Win32, I would probably have defined a windows message and fired off the mouse position to the top window in a message. I'm not sure if the method I'm using is a C# feature or not - I don't think you can do this sort of thing in C++/MFC. Maybe it works because all my classes are in the same namespace, and it interests me because in MFC Windows, I often go to the top window and work my way down to the class or control I need, but here I am able to work up from a lower level to a higher level.
private void SendMousePositionToParent(System.Windows.Forms.MouseEventArgs e)
{
int X = e.X - this.iLeftOffset;
int Y = e.Y;
((drawForm)this.Parent).ReceiveViewInfo(X , Y , strType);
}
Points of Interest
Some stuff we learned that is in the code but not in this article includes: changing the cursor, cursor hot point, icons.
Bezier curves - All I wanted to do was to draw the ellipse bounded by a rectangle, but my first few tries with Bezier curves left me a little bumfuzzled. Once I got them working I put all the Bezier stuff in to a Curves class, where I wouldn't have to think of them anymore. Maybe this class might help people who are struggling with the basic syntax.
In particular, the call to glMap1d has as one of its parameters a pointer to an array, and in C# you can't have pointers outside of "unsafe code", and you can look at the subroutine to see how it is done. You must also go to Project/Properties/Configuation Properties/Build/Allow Unsafe Code Blocks and set it to "true". If you're going to use this.
private unsafe void BezierDrawLine( double [,] inPoints )
Error interpretation - when we had just about given up on CSGL for selecting an object, we stepped back, took a deep breath, and decided to try to collect some better data. You don't get much of a message when you have a real, live, hairy openGL error- it just dies. Fortunately it emits an integer which can be captured and interpreted. Also, all the errors were always belonging to me - not the csgl library. In the future I might consider a debug version of the library to trace into, but for the time being, I'm using the following subroutine to interpret errors:
void PrintGLError( uint err, String from)
{
if ( err == GL.GL_NO_ERROR ) return;
if ( from != "" ) System.Console.WriteLine("GL Error: " + from);
PrintGLError( err);
}
void PrintGLError( uint err)
{
String pretty = "";
switch ( err )
{
case GL.GL_NO_ERROR:
pretty = "GL_NO_ERROR ";
pretty = pretty + "No error has been recorded."+
" The value of this symbolic constant is "+
"guaranteed to be zero. ";
break;
case GL.GL_INVALID_ENUM:
pretty = "GL_INVALID_ENUM ";
pretty = pretty + "An unacceptable value is specified for "+
"an enumerated argument. The offending command"+
" is ignored, having no side effect other than"+
" to set the error flag ";
break;
case GL.GL_INVALID_VALUE:
pretty = "GL_INVALID_VALUE ";
pretty = pretty + "A numeric argument is out of range. "+
"The offending command is ignored, having no side "+
"effect other than to set the error flag. ";
break;
case GL.GL_INVALID_OPERATION:
pretty = "GL_INVALID_OPERATION ";
pretty = pretty + "The specified operation is not "+
"allowed in the current state. The offending command "+
"is ignored, having no side effect other than "+
"to set the error flag. ";
break;
case GL.GL_STACK_OVERFLOW:
pretty = "GL_STACK_OVERFLOW ";
pretty = pretty + "This command would cause a stack overflow."+
" The offending command is ignored, having no side"+
" effect other than to set the error flag. ";
break;
case GL.GL_STACK_UNDERFLOW:
pretty = "GL_STACK_UNDERFLOW ";
pretty = pretty + "This command would cause a stack "+
"underflow. The offending command is ignored, "+
"having no side effect other than to set "+
"the error flag. ";
break;
case GL.GL_OUT_OF_MEMORY:
pretty = "GL_OUT_OF_MEMORY ";
pretty = pretty + "There is not enough memory left to "+
"execute the command. The state of the GL is "+
"undefined, except for the state of the error "+
"flags, after this error is recorded. ";
break;
}
System.Console.WriteLine("GL Error: " + pretty);
}
PrintGLError( GL.glGetError() , "Render");
Getting the OnMouseHover
to work right - we wanted the hover event to work for each of the drawing objects displayed in the view. Normally, the hover event is associated with a control, or with the view itself, and fires only once when the mouse hovers somewhere over the view. Once it has fired, the mouse must leave the view before the hover event can fire again. In order to fake the hover behavior for each drawing object, we turned it back on after it had fired once in the view with TrackMouseEvent
. This seems to be such an obscure function that possibly the effect I am trying to achieve is Not The Windows Way, but here is a link describing it at MSDN. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/mouseinput/ mouseinputreference/mouseinputfunctions/trackmouseevent.asp
Getting the select right - the purpose of the "select" subroutine in the view is to translate x,y mouse coordinates into an OpenGL object. My DrawingObjects are each collections of OpenGL lines, each identified by a uint ID. So, for example, in the free form PencilDrawingObject, there may be hundreds of individual lines, but since they all have the same ID, when the mouse is over any one of the lines we can use the OpenGL select process to retrieve the ID. Then we wanted to change the color of the selected object. I must say that even though I coded it with that intention, the first time one of those squiggly lines appeared in its highlight color seemed almost magical.
There is a super resource on how the select process works by "cornflake": (click here)
In the process of getting the selects to work, I got nothing but obscure errors for days. I had to get some simpler code to work just to regain my confidence in CSGL. I'm including the NeHe rectangle picking example which I redid as a Windows app. (the NeHe examples by Randy Ridge produce Dos apps, and were not done with Visual Studio) . Once I got my confidence back, and instrumented all the likely error-producing sections of code, I saw that my worst error was using integers to draw lines in "draw" mode, and floating points when I called the rendering subroutine in "select" mode. OpenGL doesn't like that.
icons - All I wanted to do was change the system tray icon, and I had an undue amount of difficulty. The following is ugly, but it worked. Credits are in comments.
Bitmap bitmap = new Bitmap( "..\\..\\Graphics\\pencil16.bmp");
IntPtr hIcon = bitmap.GetHicon();
Icon icon = Icon.FromHandle(hIcon);
this.Icon = icon;
In retrospect, it looks like the best place to change the icon is with the Project Properties, rather than the above. I just didn't see it at the time.
csgl.dll - Must be downloaded from SourceForge.
History