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