colobot/src/app/app.cpp

769 lines
20 KiB
C++
Raw Normal View History

// * This file is part of the COLOBOT source code
// * Copyright (C) 2001-2008, Daniel ROUX & EPSITEC SA, www.epsitec.ch
// * Copyright (C) 2012, Polish Portal of Colobot (PPC)
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see http://www.gnu.org/licenses/.
// app.cpp
#include "app/app.h"
#include "app/system.h"
#include "common/logger.h"
#include "common/iman.h"
#include "graphics/opengl/gldevice.h"
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <stdio.h>
//! Interval of timer called to update joystick state
const int JOYSTICK_TIMER_INTERVAL = 1000/30;
//! Function called by the timer
Uint32 JoystickTimerCallback(Uint32 interval, void *);
/**
2012-06-26 20:50:55 +00:00
* \struct ApplicationPrivate
* \brief Private data of CApplication class
*
* Contains SDL-specific variables that should not be visible outside application module.
*/
struct ApplicationPrivate
{
//! Display surface
SDL_Surface *surface;
//! Currently handled event
SDL_Event currentEvent;
//! Joystick
SDL_Joystick *joystick;
//! Index of joystick device
int joystickIndex;
//! Id of joystick timer
SDL_TimerID joystickTimer;
//! Current configuration of OpenGL display device
Gfx::GLDeviceConfig deviceConfig;
ApplicationPrivate()
{
memset(&currentEvent, 0, sizeof(SDL_Event));
surface = NULL;
joystick = NULL;
joystickIndex = 0;
joystickTimer = 0;
}
};
CApplication* CApplication::m_appInstance = NULL;
CApplication::CApplication()
{
assert(m_appInstance == NULL);
m_appInstance = this;
m_private = new ApplicationPrivate();
m_exitCode = 0;
m_iMan = new CInstanceManager();
m_eventQueue = new CEventQueue(m_iMan);
m_engine = NULL;
m_device = NULL;
m_robotMain = NULL;
m_sound = NULL;
m_keyState = 0;
m_axeKey = Math::Vector(0.0f, 0.0f, 0.0f);
m_axeJoy = Math::Vector(0.0f, 0.0f, 0.0f);
m_active = false;
m_activateApp = false;
m_ready = false;
m_joystickEnabled = false;
m_time = 0.0f;
m_windowTitle = "COLOBOT";
m_showStats = false;
m_debugMode = false;
m_setupMode = true;
ResetKey();
}
CApplication::~CApplication()
{
delete m_private;
m_private = NULL;
delete m_eventQueue;
m_eventQueue = NULL;
delete m_iMan;
m_iMan = NULL;
m_appInstance = NULL;
}
Error CApplication::ParseArguments(int argc, char *argv[])
{
for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
if (arg == "-debug")
{
m_showStats = true;
SetDebugMode(true);
}
// TODO else {} report invalid argument
}
return ERR_OK;
}
bool CApplication::Create()
{
// Temporarily -- only in windowed mode
m_private->deviceConfig.fullScreen = false;
// Create the 3D engine
m_engine = new Gfx::CEngine(m_iMan, this);
// Initialize the app's custom scene stuff, but before initializing the graphics device
if (! m_engine->BeforeCreateInit())
{
SystemDialog(SDT_ERROR, "COLOBOT - Error", std::string("Error in CEngine::BeforeCreateInit() :\n") +
std::string(m_engine->GetError()) );
m_exitCode = 1;
return false;
}
/* // Create the sound instance.
m_sound = new CSound(m_iMan);
// Create the robot application.
m_robotMain = new CRobotMain(m_iMan); */
/* SDL initialization sequence */
Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_TIMER;
if (SDL_Init(initFlags) < 0)
{
SystemDialog( SDT_ERROR, "COLOBOT - Error", "SDL initialization error:\n" +
std::string(SDL_GetError()) );
m_exitCode = 2;
return false;
}
if ((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) == 0)
{
SystemDialog( SDT_ERROR, "COLOBOT - Error", std::string("SDL_Image initialization error:\n") +
std::string(IMG_GetError()) );
m_exitCode = 3;
return false;
}
const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo();
if (videoInfo == NULL)
{
SystemDialog( SDT_ERROR, "COLOBOT - Error", "SDL error while getting video info:\n " +
std::string(SDL_GetError()) );
m_exitCode = 2;
return false;
}
Uint32 videoFlags = SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE;
if (m_private->deviceConfig.resizeable)
videoFlags |= SDL_RESIZABLE;
// Use hardware surface if available
if (videoInfo->hw_available)
videoFlags |= SDL_HWSURFACE;
else
videoFlags |= SDL_SWSURFACE;
// Enable hardware blit if available
if (videoInfo->blit_hw)
videoFlags |= SDL_HWACCEL;
if (m_private->deviceConfig.fullScreen)
videoFlags |= SDL_FULLSCREEN;
// Set OpenGL attributes
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, m_private->deviceConfig.redSize);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, m_private->deviceConfig.greenSize);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, m_private->deviceConfig.blueSize);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, m_private->deviceConfig.alphaSize);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, m_private->deviceConfig.depthSize);
if (m_private->deviceConfig.doubleBuf)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
/* If hardware acceleration specifically requested, this will force the hw accel
and fail with error if not available */
if (m_private->deviceConfig.hardwareAccel)
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
m_private->surface = SDL_SetVideoMode(m_private->deviceConfig.width, m_private->deviceConfig.height,
m_private->deviceConfig.bpp, videoFlags);
if (m_private->surface == NULL)
{
SystemDialog( SDT_ERROR, "COLOBT - Error", std::string("SDL error while setting video mode:\n") +
std::string(SDL_GetError()) );
m_exitCode = 2;
return false;
}
SDL_WM_SetCaption(m_windowTitle.c_str(), m_windowTitle.c_str());
// Enable translating key codes of key press events to unicode chars
SDL_EnableUNICODE(1);
// Don't generate joystick events
SDL_JoystickEventState(SDL_IGNORE);
// For now, enable joystick for testing
SetJoystickEnabled(true);
// The video is ready, we can create and initalize the graphics device
m_device = new Gfx::CGLDevice();
if (! m_device->Create() )
{
SystemDialog( SDT_ERROR, "COLOBT - Error", std::string("Error in CDevice::Create() :\n") +
std::string(m_device->GetError()) );
m_exitCode = 1;
return false;
}
m_engine->SetDevice(m_device);
if (! m_engine->Create() )
{
SystemDialog( SDT_ERROR, "COLOBT - Error", std::string("Error in CEngine::Create() :\n") +
std::string(m_engine->GetError()) );
m_exitCode = 1;
return false;
}
if (! m_engine->AfterDeviceSetInit() )
{
SystemDialog( SDT_ERROR, "COLOBT - Error", std::string("Error in CEngine::AfterDeviceSetInit() :\n") +
std::string(m_engine->GetError()) );
m_exitCode = 1;
return false;
}
// The app is ready to go
m_ready = true;
return true;
}
void CApplication::Destroy()
{
/*if (m_robotMain != NULL)
{
delete m_robotMain;
m_robotMain = NULL;
}
if (m_sound != NULL)
{
delete m_sound;
m_sound = NULL;
}*/
if (m_engine != NULL)
{
if (m_engine->GetWasInit())
m_engine->Destroy();
delete m_engine;
m_engine = NULL;
}
if (m_device != NULL)
{
if (m_device->GetWasInit())
m_device->Destroy();
delete m_device;
m_device = NULL;
}
if (m_private->joystick != NULL)
{
SDL_JoystickClose(m_private->joystick);
m_private->joystick = NULL;
}
if (m_private->surface != NULL)
{
SDL_FreeSurface(m_private->surface);
m_private->surface = NULL;
}
IMG_Quit();
SDL_Quit();
}
bool CApplication::OpenJoystick()
{
m_private->joystick = SDL_JoystickOpen(m_private->joystickIndex);
if (m_private->joystick == NULL)
return false;
// Create the vectors with joystick axis & button states to exactly the required size
m_joyAxeState = std::vector<int>(SDL_JoystickNumAxes(m_private->joystick), 0);
m_joyButtonState = std::vector<bool>(SDL_JoystickNumButtons(m_private->joystick), false);
// Create a timer for polling joystick state
m_private->joystickTimer = SDL_AddTimer(JOYSTICK_TIMER_INTERVAL, JoystickTimerCallback, NULL);
return true;
}
void CApplication::CloseJoystick()
{
// Timer will remove itself automatically
SDL_JoystickClose(m_private->joystick);
m_private->joystick = NULL;
}
Uint32 JoystickTimerCallback(Uint32 interval, void *)
{
CApplication *app = CApplication::GetInstance();
if ((app == NULL) || (! app->GetJoystickEnabled()))
return 0; // don't run the timer again
app->UpdateJoystick();
return interval; // run for the same interval again
}
/** Updates the state info in CApplication and on change, creates SDL events and pushes them to SDL event queue.
This way, the events get handled properly in the main event loop and besides, SDL_PushEvent() ensures thread-safety. */
void CApplication::UpdateJoystick()
{
if (! m_joystickEnabled)
return;
SDL_JoystickUpdate();
for (int axis = 0; axis < (int) m_joyAxeState.size(); ++axis)
{
int newValue = SDL_JoystickGetAxis(m_private->joystick, axis);
if (m_joyAxeState[axis] != newValue)
{
m_joyAxeState[axis] = newValue;
SDL_Event joyAxisEvent;
joyAxisEvent.jaxis.type = SDL_JOYAXISMOTION;
joyAxisEvent.jaxis.which = 0;
joyAxisEvent.jaxis.axis = axis;
joyAxisEvent.jaxis.value = newValue;
SDL_PushEvent(&joyAxisEvent);
}
}
for (int button = 0; button < (int) m_joyButtonState.size(); ++button)
{
bool newValue = SDL_JoystickGetButton(m_private->joystick, button) == 1;
if (m_joyButtonState[button] != newValue)
{
m_joyButtonState[button] = newValue;
SDL_Event joyButtonEvent;
if (newValue)
{
joyButtonEvent.jbutton.type = SDL_JOYBUTTONDOWN;
joyButtonEvent.jbutton.state = SDL_PRESSED;
}
else
{
joyButtonEvent.jbutton.type = SDL_JOYBUTTONUP;
joyButtonEvent.jbutton.state = SDL_RELEASED;
}
joyButtonEvent.jbutton.which = 0;
joyButtonEvent.jbutton.button = button;
SDL_PushEvent(&joyButtonEvent);
}
}
}
int CApplication::Run()
{
m_active = true;
while (m_private->currentEvent.type != SDL_QUIT)
{
// Use SDL_PeepEvents() if the app is active, so we can use idle time to
// render the scene. Else, use SDL_PollEvent() to avoid eating CPU time.
int count = 0;
if (m_active)
{
SDL_PumpEvents();
count = SDL_PeepEvents(&m_private->currentEvent, 1, SDL_GETEVENT, SDL_ALLEVENTS);
}
else
{
SDL_PollEvent(&m_private->currentEvent);
}
// If received an event
if ((m_active && count > 0) || (!m_active))
{
ParseEvent();
}
// Render a frame during idle time (no messages are waiting)
if (m_active && m_ready)
{
Event event;
while (m_eventQueue->GetEvent(event))
{
if (event.type == EVENT_QUIT)
{
goto end; // exit both loops
}
//m_robotMain->EventProcess(event);
}
// If an error occurs, push quit event to the queue
if (! Render())
{
SDL_Event quitEvent;
memset(&quitEvent, 0, sizeof(SDL_Event));
quitEvent.type = SDL_QUIT;
SDL_PushEvent(&quitEvent);
}
}
}
end:
//m_sound->StopMusic();
Destroy();
return m_exitCode;
}
int CApplication::GetExitCode()
{
return m_exitCode;
}
//! Translates SDL press state to PressState
PressState TranslatePressState(unsigned char state)
{
if (state == SDL_PRESSED)
return STATE_PRESSED;
else
return STATE_RELEASED;
}
/** Conversion of the position of the mouse to the following coordinates:
x: 0=left, 1=right
y: 0=down, 1=up
*/
Math::Point CApplication::WindowToInterfaceCoords(int x, int y)
{
return Math::Point((float)x / (float)m_private->deviceConfig.width,
1.0f - (float)y / (float)m_private->deviceConfig.height);
}
void CApplication::ParseEvent()
{
Event event;
if ( (m_private->currentEvent.type == SDL_KEYDOWN) ||
(m_private->currentEvent.type == SDL_KEYUP) )
{
if (m_private->currentEvent.type == SDL_KEYDOWN)
event.type = EVENT_KEY_DOWN;
else
event.type = EVENT_KEY_UP;
event.key.key = m_private->currentEvent.key.keysym.sym;
event.key.mod = m_private->currentEvent.key.keysym.mod;
event.key.state = TranslatePressState(m_private->currentEvent.key.state);
event.key.unicode = m_private->currentEvent.key.keysym.unicode;
}
else if ( (m_private->currentEvent.type == SDL_MOUSEBUTTONDOWN) ||
(m_private->currentEvent.type == SDL_MOUSEBUTTONUP) )
{
if (m_private->currentEvent.type == SDL_MOUSEBUTTONDOWN)
event.type = EVENT_MOUSE_BUTTON_DOWN;
else
event.type = EVENT_MOUSE_BUTTON_UP;
event.mouseButton.button = m_private->currentEvent.button.button;
event.mouseButton.state = TranslatePressState(m_private->currentEvent.button.state);
event.mouseButton.pos = WindowToInterfaceCoords(m_private->currentEvent.button.x, m_private->currentEvent.button.y);
}
else if (m_private->currentEvent.type == SDL_MOUSEMOTION)
{
event.type = EVENT_MOUSE_MOVE;
event.mouseMove.state = TranslatePressState(m_private->currentEvent.button.state);
event.mouseMove.pos = WindowToInterfaceCoords(m_private->currentEvent.button.x, m_private->currentEvent.button.y);
}
// TODO: joystick state polling instead of getting events
else if (m_private->currentEvent.type == SDL_JOYAXISMOTION)
{
event.type = EVENT_JOY_AXIS;
event.joyAxis.axis = m_private->currentEvent.jaxis.axis;
event.joyAxis.value = m_private->currentEvent.jaxis.value;
}
else if ( (m_private->currentEvent.type == SDL_JOYBUTTONDOWN) ||
(m_private->currentEvent.type == SDL_JOYBUTTONUP) )
{
if (m_private->currentEvent.type == SDL_JOYBUTTONDOWN)
event.type = EVENT_JOY_BUTTON_DOWN;
else
event.type = EVENT_JOY_BUTTON_UP;
event.joyButton.button = m_private->currentEvent.jbutton.button;
event.joyButton.state = TranslatePressState(m_private->currentEvent.jbutton.state);
}
if (m_robotMain != NULL && event.type != EVENT_NULL)
{
//m_robotMain->EventProcess(event);
}
ProcessEvent(event);
}
void CApplication::ProcessEvent(Event event)
{
CLogger *l = GetLogger();
// Print the events in debug mode to test the code
if (m_debugMode)
{
switch (event.type)
{
case EVENT_KEY_DOWN:
case EVENT_KEY_UP:
l->Info("EVENT_KEY_%s:\n", (event.type == EVENT_KEY_DOWN) ? "DOWN" : "UP");
l->Info(" key = %4x\n", event.key.key);
l->Info(" state = %s\n", (event.key.state == STATE_PRESSED) ? "STATE_PRESSED" : "STATE_RELEASED");
l->Info(" mod = %4x\n", event.key.mod);
l->Info(" unicode = %4x\n", event.key.unicode);
break;
case EVENT_MOUSE_MOVE:
l->Info("EVENT_MOUSE_MOVE:\n");
l->Info(" state = %s\n", (event.mouseMove.state == STATE_PRESSED) ? "STATE_PRESSED" : "STATE_RELEASED");
l->Info(" pos = (%f, %f)\n", event.mouseMove.pos.x, event.mouseMove.pos.y);
break;
case EVENT_MOUSE_BUTTON_DOWN:
case EVENT_MOUSE_BUTTON_UP:
l->Info("EVENT_MOUSE_BUTTON_%s:\n", (event.type == EVENT_MOUSE_BUTTON_DOWN) ? "DOWN" : "UP");
l->Info(" button = %d\n", event.mouseButton.button);
l->Info(" state = %s\n", (event.mouseButton.state == STATE_PRESSED) ? "STATE_PRESSED" : "STATE_RELEASED");
l->Info(" pos = (%f, %f)\n", event.mouseButton.pos.x, event.mouseButton.pos.y);
break;
case EVENT_JOY_AXIS:
l->Info("EVENT_JOY_AXIS:\n");
l->Info(" axis = %d\n", event.joyAxis.axis);
l->Info(" value = %d\n", event.joyAxis.value);
break;
case EVENT_JOY_BUTTON_DOWN:
case EVENT_JOY_BUTTON_UP:
l->Info("EVENT_JOY_BUTTON_%s:\n", (event.type == EVENT_JOY_BUTTON_DOWN) ? "DOWN" : "UP");
l->Info(" button = %d\n", event.joyButton.button);
l->Info(" state = %s\n", (event.joyButton.state == STATE_PRESSED) ? "STATE_PRESSED" : "STATE_RELEASED");
break;
default:
break;
}
}
}
bool CApplication::Render()
{
bool result = m_engine->Render();
if (! result)
return false;
if (m_private->deviceConfig.doubleBuf)
SDL_GL_SwapBuffers();
return true;
}
/** Called in to toggle the pause state of the app. */
void CApplication::Pause(bool pause)
{
static long appPausedCount = 0L;
appPausedCount += ( pause ? +1 : -1 );
m_ready = appPausedCount == 0;
// Handle the first pause request (of many, nestable pause requests)
if( pause && ( 1 == appPausedCount ) )
{
// Stop the scene from animating
//m_engine->TimeEnterGel();
}
// Final pause request done
if (appPausedCount == 0)
{
// Restart the scene
//m_engine->TimeExitGel();
}
}
void CApplication::StepSimulation(float rTime)
{
// TODO
}
void CApplication::SetShowStat(bool show)
{
m_showStats = show;
}
bool CApplication::GetShowStat()
{
return m_showStats;
}
void CApplication::SetDebugMode(bool mode)
{
m_debugMode = mode;
}
bool CApplication::GetDebugMode()
{
return m_debugMode;
}
bool CApplication::GetSetupMode()
{
return m_setupMode;
}
void CApplication::FlushPressKey()
{
// TODO
}
void CApplication::ResetKey()
{
// TODO
}
void CApplication::SetKey(int keyRank, int option, int key)
{
// TODO
}
int CApplication::GetKey(int keyRank, int option)
{
// TODO
return 0;
}
void CApplication::SetMousePos(Math::Point pos)
{
// TODO
}
void CApplication::SetMouseType(Gfx::MouseType type)
{
// TODO
}
void CApplication::SetJoystickEnabled(bool enable)
{
m_joystickEnabled = enable;
if (m_joystickEnabled)
{
if (! OpenJoystick())
{
m_joystickEnabled = false;
}
}
else
{
CloseJoystick();
}
}
bool CApplication::GetJoystickEnabled()
{
return m_joystickEnabled;
}
bool CApplication::WriteScreenShot(char *filename, int width, int height)
{
// TODO
return false;
}
void CApplication::InitText()
{
// TODO
}
void CApplication::DrawSuppl()
{
// TODO
}
void CApplication::ShowStats()
{
// TODO
}
void CApplication::OutputText(long x, long y, char* str)
{
// TODO
}