1 //
2 // Copyright (C) 2009-2010 Nick Gasson
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 //
17
18 #include "IWindow.hpp"
19 #include "ILogger.hpp"
20 #include "IPickBuffer.hpp"
21 #include "Maths.hpp"
22 #include "OpenGLHelper.hpp"
23 #include "IConfig.hpp"
24 #include "IMesh.hpp"
25
26 #include <stdexcept>
27 #include <sstream>
28 #include <cstdlib>
29 #include <cassert>
30
31 #include <boost/lexical_cast.hpp>
32 #include <SDL.h>
33 #include <GL/gl.h>
34 #include <GL/glu.h>
35
36 // Concrete implementation of SDL window
37 class SDLWindow : public IWindow, public IGraphics, public IPickBuffer,
38 public enable_shared_from_this<SDLWindow> {
39 public:
40 SDLWindow();
41 ~SDLWindow();
42
43 // IWindow interface
44 void run(IScreenPtr a_screen, int frames);
45 void switch_screen(IScreenPtr a_screen);
46 void quit();
47 void take_screen_shot();
width() const48 int width() const { return width_; }
height() const49 int height() const { return height_; }
redraw_hint()50 void redraw_hint() {}
51 int get_fps() const;
52
53 // IGraphics interface
54 bool cuboid_in_view_frustum(float x, float y, float z,
55 float sizeX, float sizeY, float sizeZ);
56 bool cube_in_view_frustum(float x, float y, float z, float size);
57 bool point_in_view_frustum(float x, float y, float z);
58 void set_camera(const Vector<float>& a_pos,
59 const Vector<float>& a_rotation);
60 void look_at(const Vector<float> an_eye_point,
61 const Vector<float> a_target_point);
62
63 // IPickBuffer interface
64 IGraphicsPtr begin_pick(int x, int y);
65 unsigned end_pick();
66 private:
67 void process_input();
68 MouseButton from_sdl_button(Uint8 aSDLButton) const;
69 void capture_frame() const;
70
71 bool am_running;
72 int width_, height_;
73 IScreenPtr screen;
74 bool will_skip_next_frame;
75 bool will_take_screen_shot;
76 Frustum view_frustum;
77
78 // Picking data
79 static const int SELECT_BUFFER_SZ = 128;
80 GLuint my_select_buffer[SELECT_BUFFER_SZ];
81 };
82
83 // Calculation and display of the FPS rate
84 namespace {
85 int the_frame_counter = 0;
86 int the_last_fps = 0;
87 }
88
updateFPS(Uint32 an_interval,void * thread)89 static Uint32 updateFPS(Uint32 an_interval, void* thread)
90 {
91 the_last_fps = the_frame_counter;
92 the_frame_counter = 0;
93
94 return an_interval;
95 }
96
frame_complete()97 static void frame_complete()
98 {
99 the_frame_counter++;
100
101 update_render_stats();
102 }
103
104 // A wrapper around SDL times
105 struct FrameTimerThread {
FrameTimerThreadFrameTimerThread106 FrameTimerThread()
107 {
108 my_timer = SDL_AddTimer(1000, updateFPS, this);
109 }
110
~FrameTimerThreadFrameTimerThread111 ~FrameTimerThread()
112 {
113 // Finalise properly when an exception is thrown
114 SDL_RemoveTimer(my_timer);
115 }
116
117 SDL_TimerID my_timer;
118 };
119
120 // Create the game window
SDLWindow()121 SDLWindow::SDLWindow()
122 : am_running(false), will_skip_next_frame(false),
123 will_take_screen_shot(false)
124 {
125 IConfigPtr cfg = get_config();
126
127 // Start SDL
128 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
129 ostringstream ss;
130 ss << "Unable to initialise SDL: " << SDL_GetError();
131 throw runtime_error(ss.str());
132 }
133 atexit(SDL_Quit);
134
135 // Set the video mode
136 cfg->get("XRes", width_);
137 cfg->get("YRes", height_);
138
139 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
140 if (SDL_SetVideoMode(width_, height_, 0, SDL_OPENGL) == NULL) {
141 ostringstream ss;
142 ss << "Unable to create OpenGL screen: " << SDL_GetError();
143 throw runtime_error(ss.str());
144 }
145
146 SDL_WM_SetCaption("Trains!", NULL);
147
148 // Turn on key repeat
149 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
150
151 // Hide the window manager cursor
152 //SDL_ShowCursor(SDL_DISABLE);
153
154 // Start OpenGL
155 printGLVersion();
156 initGL();
157
158 log() << "Created " << width_ << "x" << height_ << " window";
159 }
160
161 // Destroy the game window
~SDLWindow()162 SDLWindow::~SDLWindow()
163 {
164
165 }
166
167 // Make a screen capture at the end of this frame
take_screen_shot()168 void SDLWindow::take_screen_shot()
169 {
170 will_take_screen_shot = true;
171 }
172
173 // Change the active screen while the game is running
switch_screen(IScreenPtr a_screen)174 void SDLWindow::switch_screen(IScreenPtr a_screen)
175 {
176 assert(am_running);
177
178 screen = a_screen;
179 will_skip_next_frame = true;
180 }
181
182 // Run the game until the user quits or the frame limit runs out
run(IScreenPtr a_screen,int frames)183 void SDLWindow::run(IScreenPtr a_screen, int frames)
184 {
185 assert(!am_running);
186
187 screen = a_screen;
188
189 FrameTimerThread fps_timer;
190
191 unsigned last_tick = SDL_GetTicks();
192
193 // Wait a few milliseconds to get a reasonable tick delta
194 SDL_Delay(1);
195
196 am_running = true;
197 do {
198 unsigned tick_start = SDL_GetTicks();
199 int delta = static_cast<int>(tick_start - last_tick);
200
201 try {
202 process_input();
203 screen->update(shared_from_this(), delta);
204
205 if (!will_skip_next_frame) {
206 drawGLScene(shared_from_this(), shared_from_this(), screen);
207 SDL_GL_SwapBuffers();
208 }
209 else
210 will_skip_next_frame = false;
211 }
212 catch (runtime_error& e) {
213 error() << "Caught exception: " << e.what();
214 am_running = false;
215 }
216
217 if (will_take_screen_shot) {
218 capture_frame();
219 will_take_screen_shot = false;
220 }
221
222 if (frames > 0) {
223 am_running = (--frames > 0);
224 }
225
226 frame_complete();
227 //fps_timer.update_title();
228 last_tick = tick_start;
229 update_render_stats();
230 } while (am_running);
231
232 screen.reset();
233 }
234
235 // Stop the game cleanly
quit()236 void SDLWindow::quit()
237 {
238 am_running = false;
239 }
240
241 // Convert an SDL button constant to a MouseButton
from_sdl_button(Uint8 button) const242 MouseButton SDLWindow::from_sdl_button(Uint8 button) const
243 {
244 switch (button) {
245 case SDL_BUTTON_LEFT: return MOUSE_LEFT;
246 case SDL_BUTTON_MIDDLE: return MOUSE_MIDDLE;
247 case SDL_BUTTON_RIGHT: return MOUSE_RIGHT;
248 case SDL_BUTTON_WHEELUP: return MOUSE_WHEEL_UP;
249 case SDL_BUTTON_WHEELDOWN: return MOUSE_WHEEL_DOWN;
250 default:
251 return MOUSE_UNKNOWN;
252 }
253 }
254
255 // Check for SDL input events
process_input()256 void SDLWindow::process_input()
257 {
258 SDL_Event e;
259
260 // Send only one mouse motion event per frame
261 bool have_sent_mouseMotion = false;
262
263 while (SDL_PollEvent(&e)) {
264 switch (e.type) {
265 case SDL_QUIT:
266 // End the game
267 quit();
268 log() << "Window closed";
269 break;
270
271 case SDL_KEYDOWN:
272 screen->on_key_down(e.key.keysym.sym);
273 break;
274
275 case SDL_KEYUP:
276 screen->on_key_up(e.key.keysym.sym);
277 break;
278
279 case SDL_MOUSEMOTION:
280 if (!have_sent_mouseMotion) {
281 screen->on_mouse_move(shared_from_this(),
282 e.motion.x, e.motion.y,
283 e.motion.xrel, e.motion.yrel);
284 have_sent_mouseMotion = true;
285 }
286 break;
287
288 case SDL_MOUSEBUTTONDOWN:
289 screen->on_mouse_click(shared_from_this(),
290 e.button.x, e.button.y,
291 from_sdl_button(e.button.button));
292 break;
293
294 case SDL_MOUSEBUTTONUP:
295 screen->on_mouse_release(shared_from_this(),
296 e.button.x, e.button.y,
297 from_sdl_button(e.button.button));
298 break;
299
300 case SDL_VIDEORESIZE:
301 width_ = e.resize.w;
302 height_ = e.resize.h;
303
304 resizeGLScene(shared_from_this());
305 break;
306 }
307 }
308 }
309
310 // Set up OpenGL to pick out objects
begin_pick(int x,int y)311 IGraphicsPtr SDLWindow::begin_pick(int x, int y)
312 {
313 ::begin_pick(shared_from_this(), my_select_buffer, x, y);
314 return shared_from_this();
315 }
316
317 // Finish picking and return the name of the clicked object or zero
318 // It's *very* important that this is called exactly once for every
319 // begin_pick or things will get very messed up
end_pick()320 unsigned SDLWindow::end_pick()
321 {
322 return ::end_pick(my_select_buffer);
323 }
324
325 // Called to set the camera position
set_camera(const Vector<float> & a_pos,const Vector<float> & a_rotation)326 void SDLWindow::set_camera(const Vector<float>& a_pos,
327 const Vector<float>& a_rotation)
328 {
329 glRotatef(a_rotation.x, 1.0f, 0.0f, 0.0f);
330 glRotatef(a_rotation.y, 0.0f, 1.0f, 0.0f);
331 glRotatef(a_rotation.z, 0.0f, 0.0f, 1.0f);
332 glTranslatef(a_pos.x, a_pos.y, a_pos.z);
333
334 view_frustum = get_view_frustum();
335 }
336
337 // A wrapper around glu_look_at
look_at(const Vector<float> an_eye_point,const Vector<float> a_target_point)338 void SDLWindow::look_at(const Vector<float> an_eye_point,
339 const Vector<float> a_target_point)
340 {
341 gluLookAt(an_eye_point.x, an_eye_point.y, an_eye_point.z,
342 a_target_point.x, a_target_point.y, a_target_point.z,
343 0, 1, 0);
344
345 view_frustum = get_view_frustum();
346 }
347
348 // Intersect a cuboid with the current view frustum
cuboid_in_view_frustum(float x,float y,float z,float sizeX,float sizeY,float sizeZ)349 bool SDLWindow::cuboid_in_view_frustum(float x, float y, float z,
350 float sizeX, float sizeY, float sizeZ)
351 {
352 return view_frustum.cuboid_in_frustum(x, y, z, sizeX, sizeY, sizeZ);
353 }
354
355 // Intersect a cube with the current view frustum
cube_in_view_frustum(float x,float y,float z,float size)356 bool SDLWindow::cube_in_view_frustum(float x, float y, float z, float size)
357 {
358 return view_frustum.cube_in_frustum(x, y, z, size);
359 }
360
361 // True if the point is contained within the view frustum
point_in_view_frustum(float x,float y,float z)362 bool SDLWindow::point_in_view_frustum(float x, float y, float z)
363 {
364 return view_frustum.point_in_frustum(x, y, z);
365 }
366
367 // Capture the OpenGL pixels and save them to a file
capture_frame() const368 void SDLWindow::capture_frame() const
369 {
370 static int file_number = 1;
371
372 const string file_name
373 ("screenshot" + boost::lexical_cast<string>(file_number++) + ".bmp");
374
375 SDL_Surface* temp = SDL_CreateRGBSurface
376 (SDL_SWSURFACE, width_, height_, 24,
377 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
378 0x000000FF, 0x0000FF00, 0x00FF0000, 0
379 #else
380 0x00FF0000, 0x0000FF00, 0x000000FF, 0
381 #endif
382 );
383 assert(temp);
384
385 const int w = width_;
386 const int h = height_;
387 unsigned char* pixels = new unsigned char[3 * w * h];
388
389 glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
390
391 for (int i = 0; i < h; i++)
392 memcpy(((char*)temp->pixels) + temp->pitch * i, pixels + 3*w * (h-i-1), w*3);
393 delete[] pixels;
394
395 SDL_SaveBMP(temp, file_name.c_str());
396 SDL_FreeSurface(temp);
397
398 log() << "Wrote screen shot to " << file_name;
399 }
400
get_fps() const401 int SDLWindow::get_fps() const
402 {
403 return ::the_last_fps;
404 }
405
406 // Construct and initialise an OpenGL SDL window
make_sdl_window()407 IWindowPtr make_sdl_window()
408 {
409 return make_shared<SDLWindow>();
410 }
411