update the samples to use new (non-deprecated) wxGLCanvas API; added more comments...
[wxWidgets.git] / samples / opengl / cube / cube.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: cube.cpp
3 // Purpose: wxGLCanvas demo program
4 // Author: Julian Smart
5 // Modified by: Vadim Zeitlin to use new wxGLCanvas API (2007-04-09)
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #ifndef WX_PRECOMP
28 #include "wx/wx.h"
29 #endif
30
31 #if !wxUSE_GLCANVAS
32 #error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
33 #endif
34
35 #include "cube.h"
36
37 #if !defined(__WXMSW__) && !defined(__WXPM__)
38 #include "../../sample.xpm"
39 #endif
40
41 // ----------------------------------------------------------------------------
42 // helper functions
43 // ----------------------------------------------------------------------------
44
45 static void CheckGLError()
46 {
47 GLenum errLast = GL_NO_ERROR;
48
49 for ( ;; )
50 {
51 GLenum err = glGetError();
52 if ( err == GL_NO_ERROR )
53 return;
54
55 // normally the error is reset by the call to glGetError() but if
56 // glGetError() itself returns an error, we risk looping forever here
57 // so check that we get a different error than the last time
58 if ( err == errLast )
59 {
60 wxLogError(_T("OpenGL error state couldn't be reset."));
61 return;
62 }
63
64 errLast = err;
65
66 wxLogError(_T("OpenGL error %d"), err);
67 }
68 }
69
70 // function to draw the texture for cube faces
71 static wxImage DrawDice(int size, unsigned num)
72 {
73 wxASSERT_MSG( num >= 1 && num <= 6, _T("invalid dice index") );
74
75 const int dot = size/16; // radius of a single dot
76 const int gap = 5*size/32; // gap between dots
77
78 wxBitmap bmp(size, size);
79 wxMemoryDC dc;
80 dc.SelectObject(bmp);
81 dc.SetBackground(*wxWHITE_BRUSH);
82 dc.Clear();
83 dc.SetBrush(*wxBLACK_BRUSH);
84
85 // the upper left and lower right points
86 if ( num != 1 )
87 {
88 dc.DrawCircle(gap + dot, gap + dot, dot);
89 dc.DrawCircle(size - gap - dot, size - gap - dot, dot);
90 }
91
92 // draw the central point for odd dices
93 if ( num % 2 )
94 {
95 dc.DrawCircle(size/2, size/2, dot);
96 }
97
98 // the upper right and lower left points
99 if ( num > 3 )
100 {
101 dc.DrawCircle(size - gap - dot, gap + dot, dot);
102 dc.DrawCircle(gap + dot, size - gap - dot, dot);
103 }
104
105 // finally those 2 are only for the last dice
106 if ( num == 6 )
107 {
108 dc.DrawCircle(gap + dot, size/2, dot);
109 dc.DrawCircle(size - gap - dot, size/2, dot);
110 }
111
112 dc.SelectObject(wxNullBitmap);
113
114 return bmp.ConvertToImage();
115 }
116
117 // ============================================================================
118 // implementation
119 // ============================================================================
120
121 // ----------------------------------------------------------------------------
122 // TestGLContext
123 // ----------------------------------------------------------------------------
124
125 TestGLContext::TestGLContext(wxGLCanvas *canvas)
126 : wxGLContext(canvas)
127 {
128 SetCurrent(*canvas);
129
130 // set up the parameters we want to use
131 glEnable(GL_DEPTH_TEST);
132 glEnable(GL_LIGHTING);
133 glEnable(GL_LIGHT0);
134 glEnable(GL_TEXTURE_2D);
135
136 // add slightly more light, the default lighting is rather dark
137 GLfloat ambient[] = { 0.5, 0.5, 0.5, 0.5 };
138 glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
139
140 // set viewing projection
141 glMatrixMode(GL_PROJECTION);
142 glLoadIdentity();
143 glFrustum(-0.5f, 0.5f, -0.5f, 0.5f, 1.0f, 3.0f);
144
145 // create the textures to use for cube sides: they will be reused by all
146 // canvases (which is probably not critical in the case of simple textures
147 // we use here but could be really important for a real application where
148 // each texture could take many megabytes)
149 glGenTextures(WXSIZEOF(m_textures), m_textures);
150
151 for ( unsigned i = 0; i < WXSIZEOF(m_textures); i++ )
152 {
153 glBindTexture(GL_TEXTURE_2D, m_textures[i]);
154
155 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
156 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
157 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
158 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
159 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
160
161 const wxImage img(DrawDice(256, i + 1));
162
163 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
164 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.GetWidth(), img.GetHeight(),
165 0, GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
166 }
167
168 CheckGLError();
169 }
170
171 void TestGLContext::DrawRotatedCube(float xangle, float yangle)
172 {
173 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
174
175 glMatrixMode(GL_MODELVIEW);
176 glLoadIdentity();
177 glTranslatef(0.0f, 0.0f, -2.0f);
178 glRotatef(xangle, 1.0f, 0.0f, 0.0f);
179 glRotatef(yangle, 0.0f, 1.0f, 0.0f);
180
181 // draw six faces of a cube of size 1 centered at (0, 0, 0)
182 glBindTexture(GL_TEXTURE_2D, m_textures[0]);
183 glBegin(GL_QUADS);
184 glNormal3f( 0.0f, 0.0f, 1.0f);
185 glTexCoord2f(0, 0); glVertex3f( 0.5f, 0.5f, 0.5f);
186 glTexCoord2f(1, 0); glVertex3f(-0.5f, 0.5f, 0.5f);
187 glTexCoord2f(1, 1); glVertex3f(-0.5f,-0.5f, 0.5f);
188 glTexCoord2f(0, 1); glVertex3f( 0.5f,-0.5f, 0.5f);
189 glEnd();
190
191 glBindTexture(GL_TEXTURE_2D, m_textures[1]);
192 glBegin(GL_QUADS);
193 glNormal3f( 0.0f, 0.0f,-1.0f);
194 glTexCoord2f(0, 0); glVertex3f(-0.5f,-0.5f,-0.5f);
195 glTexCoord2f(1, 0); glVertex3f(-0.5f, 0.5f,-0.5f);
196 glTexCoord2f(1, 1); glVertex3f( 0.5f, 0.5f,-0.5f);
197 glTexCoord2f(0, 1); glVertex3f( 0.5f,-0.5f,-0.5f);
198 glEnd();
199
200 glBindTexture(GL_TEXTURE_2D, m_textures[2]);
201 glBegin(GL_QUADS);
202 glNormal3f( 0.0f, 1.0f, 0.0f);
203 glTexCoord2f(0, 0); glVertex3f( 0.5f, 0.5f, 0.5f);
204 glTexCoord2f(1, 0); glVertex3f( 0.5f, 0.5f,-0.5f);
205 glTexCoord2f(1, 1); glVertex3f(-0.5f, 0.5f,-0.5f);
206 glTexCoord2f(0, 1); glVertex3f(-0.5f, 0.5f, 0.5f);
207 glEnd();
208
209 glBindTexture(GL_TEXTURE_2D, m_textures[3]);
210 glBegin(GL_QUADS);
211 glNormal3f( 0.0f,-1.0f, 0.0f);
212 glTexCoord2f(0, 0); glVertex3f(-0.5f,-0.5f,-0.5f);
213 glTexCoord2f(1, 0); glVertex3f( 0.5f,-0.5f,-0.5f);
214 glTexCoord2f(1, 1); glVertex3f( 0.5f,-0.5f, 0.5f);
215 glTexCoord2f(0, 1); glVertex3f(-0.5f,-0.5f, 0.5f);
216 glEnd();
217
218 glBindTexture(GL_TEXTURE_2D, m_textures[4]);
219 glBegin(GL_QUADS);
220 glNormal3f( 1.0f, 0.0f, 0.0f);
221 glTexCoord2f(0, 0); glVertex3f( 0.5f, 0.5f, 0.5f);
222 glTexCoord2f(1, 0); glVertex3f( 0.5f,-0.5f, 0.5f);
223 glTexCoord2f(1, 1); glVertex3f( 0.5f,-0.5f,-0.5f);
224 glTexCoord2f(0, 1); glVertex3f( 0.5f, 0.5f,-0.5f);
225 glEnd();
226
227 glBindTexture(GL_TEXTURE_2D, m_textures[5]);
228 glBegin(GL_QUADS);
229 glNormal3f(-1.0f, 0.0f, 0.0f);
230 glTexCoord2f(0, 0); glVertex3f(-0.5f,-0.5f,-0.5f);
231 glTexCoord2f(1, 0); glVertex3f(-0.5f,-0.5f, 0.5f);
232 glTexCoord2f(1, 1); glVertex3f(-0.5f, 0.5f, 0.5f);
233 glTexCoord2f(0, 1); glVertex3f(-0.5f, 0.5f,-0.5f);
234 glEnd();
235
236 glFlush();
237
238 CheckGLError();
239 }
240
241
242 // ----------------------------------------------------------------------------
243 // MyApp: the application object
244 // ----------------------------------------------------------------------------
245
246 IMPLEMENT_APP(MyApp)
247
248 bool MyApp::OnInit()
249 {
250 if ( !wxApp::OnInit() )
251 return false;
252
253 new MyFrame();
254
255 return true;
256 }
257
258 int MyApp::OnExit()
259 {
260 delete m_glContext;
261
262 return wxApp::OnExit();
263 }
264
265 TestGLContext& MyApp::GetContext(wxGLCanvas *canvas)
266 {
267 if ( !m_glContext )
268 {
269 // Create the OpenGL context for the first window which needs it:
270 // subsequently created windows will all share the same context.
271 m_glContext = new TestGLContext(canvas);
272 }
273
274 m_glContext->SetCurrent(*canvas);
275
276 return *m_glContext;
277 }
278
279 // ----------------------------------------------------------------------------
280 // TestGLCanvas
281 // ----------------------------------------------------------------------------
282
283 BEGIN_EVENT_TABLE(TestGLCanvas, wxGLCanvas)
284 EVT_PAINT(TestGLCanvas::OnPaint)
285 EVT_KEY_DOWN(TestGLCanvas::OnKeyDown)
286 END_EVENT_TABLE()
287
288 TestGLCanvas::TestGLCanvas(wxWindow *parent)
289 // With perspective OpenGL graphics, the wxFULL_REPAINT_ON_RESIZE style
290 // flag should always be set, because even making the canvas smaller should
291 // be followed by a paint event that updates the entire canvas with new
292 // viewport settings.
293 : wxGLCanvas(parent, wxID_ANY, NULL /* attribs */,
294 wxDefaultPosition, wxDefaultSize,
295 wxFULL_REPAINT_ON_RESIZE)
296 {
297 m_xangle =
298 m_yangle = 30;
299 }
300
301 void TestGLCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
302 {
303 // This is required even though dc is not used otherwise.
304 wxPaintDC dc(this);
305
306 // Set the OpenGL viewport according to the client size of this canvas.
307 // This is done here rather than in a wxSizeEvent handler because our
308 // OpenGL rendering context (and thus viewport setting) is used with
309 // multiple canvases: If we updated the viewport in the wxSizeEvent
310 // handler, changing the size of one canvas causes a viewport setting that
311 // is wrong when next another canvas is repainted.
312 const wxSize ClientSize = GetClientSize();
313
314 glViewport(0, 0, ClientSize.x, ClientSize.y);
315
316 // Render the graphics and swap the buffers.
317 wxGetApp().GetContext(this).DrawRotatedCube(m_xangle, m_yangle);
318 SwapBuffers();
319 }
320
321 void TestGLCanvas::OnKeyDown( wxKeyEvent& event )
322 {
323 float *p = NULL;
324
325 bool inverse = false;
326
327 switch ( event.GetKeyCode() )
328 {
329 case WXK_RIGHT:
330 inverse = true;
331 // fall through
332
333 case WXK_LEFT:
334 // rotate around Y axis
335 p = &m_yangle;
336 break;
337
338 case WXK_DOWN:
339 inverse = true;
340 // fall through
341
342 case WXK_UP:
343 // rotate around X axis
344 p = &m_xangle;
345 break;
346
347 default:
348 event.Skip();
349 return;
350 }
351
352 float angle = 5;
353 if ( inverse )
354 angle = -angle;
355
356 *p += angle;
357
358 Refresh(false);
359 }
360
361
362 // ----------------------------------------------------------------------------
363 // MyFrame: main application window
364 // ----------------------------------------------------------------------------
365
366 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
367 EVT_MENU(wxID_NEW, MyFrame::OnNewWindow)
368 EVT_MENU(wxID_CLOSE, MyFrame::OnClose)
369 END_EVENT_TABLE()
370
371 MyFrame::MyFrame()
372 : wxFrame(NULL, wxID_ANY, _T("wxWidgets OpenGL Cube Sample"))
373 {
374 new TestGLCanvas(this);
375
376 SetIcon(wxICON(sample));
377
378 // Make a menubar
379 wxMenu *menu = new wxMenu;
380 menu->Append(wxID_NEW);
381 menu->AppendSeparator();
382 menu->Append(wxID_CLOSE);
383 wxMenuBar *menuBar = new wxMenuBar;
384 menuBar->Append(menu, _T("&Cube"));
385
386 SetMenuBar(menuBar);
387
388 CreateStatusBar();
389
390 SetClientSize(400, 400);
391 Show();
392
393 // test IsDisplaySupported() function:
394 static const int attribs[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 };
395 wxLogStatus("Double-buffered display %s supported",
396 wxGLCanvas::IsDisplaySupported(attribs) ? "is" : "not");
397 }
398
399 void MyFrame::OnClose(wxCommandEvent& WXUNUSED(event))
400 {
401 // true is to force the frame to close
402 Close(true);
403 }
404
405 void MyFrame::OnNewWindow( wxCommandEvent& WXUNUSED(event) )
406 {
407 new MyFrame();
408 }
409