4 * ***** BEGIN GPL LICENSE BLOCK *****
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
21 * All rights reserved.
23 * The Original Code is: all of this file.
25 * Contributor(s): none yet.
27 * ***** END GPL LICENSE BLOCK *****
28 * GHOST Blender Player application implementation file.
36 #pragma warning (disable:4786) // suppress stl-MSVC debug info warning
41 #include "GPU_extensions.h"
43 #include "GPG_Application.h"
46 #include <MT_assert.h>
49 /**********************************
50 * Begin Blender include block
51 **********************************/
56 #include "BLI_blenlib.h"
57 #include "BLO_readfile.h"
58 #include "BKE_global.h"
60 #include "IMB_imbuf.h"
61 #include "DNA_scene_types.h"
65 /**********************************
66 * End Blender include block
67 **********************************/
70 #include "SYS_System.h"
71 #include "KX_KetsjiEngine.h"
73 // include files needed by "KX_BlenderSceneConverter.h"
75 #include "SCA_IActuator.h"
76 #include "RAS_MeshObject.h"
77 #include "RAS_OpenGLRasterizer.h"
78 #include "RAS_VAOpenGLRasterizer.h"
79 #include "RAS_ListRasterizer.h"
80 #include "RAS_GLExtensionManager.h"
81 #include "KX_PythonInit.h"
82 #include "KX_PyConstraintBinding.h"
83 #include "BL_Material.h" // MAXTEX
85 #include "KX_BlenderSceneConverter.h"
86 #include "NG_LoopBackNetworkDeviceInterface.h"
87 #include "SND_DeviceManager.h"
89 #include "GPC_MouseDevice.h"
90 #include "GPC_RenderTools.h"
91 #include "GPG_Canvas.h"
92 #include "GPG_KeyboardDevice.h"
93 #include "GPG_System.h"
95 #include "STR_String.h"
97 #include "GHOST_ISystem.h"
98 #include "GHOST_IEvent.h"
99 #include "GHOST_IEventConsumer.h"
100 #include "GHOST_IWindow.h"
101 #include "GHOST_Rect.h"
103 static void frameTimerProc(GHOST_ITimerTask* task, GHOST_TUns64 time);
105 static GHOST_ISystem* fSystem = 0;
106 static const int kTimerFreq = 10;
108 GPG_Application::GPG_Application(GHOST_ISystem* system)
109 : m_startSceneName(""),
116 m_cursor(GHOST_kStandardCursorFirstCursor),
117 m_engineInitialized(0),
132 m_pyGlobalDictString(0),
133 m_pyGlobalDictString_Length(0)
140 GPG_Application::~GPG_Application(void)
142 if(m_pyGlobalDictString) {
143 delete [] m_pyGlobalDictString;
144 m_pyGlobalDictString = 0;
145 m_pyGlobalDictString_Length = 0;
149 fSystem->disposeWindow(m_mainWindow);
154 bool GPG_Application::SetGameEngineData(struct Main* maggie, Scene *scene)
158 if (maggie != NULL && scene != NULL)
162 m_startSceneName = scene->id.name+2;
163 m_startScene = scene;
172 #define SCR_SAVE_MOUSE_MOVE_THRESHOLD 15
174 static HWND found_ghost_window_hwnd;
175 static GHOST_IWindow* ghost_window_to_find;
176 static WNDPROC ghost_wnd_proc;
177 static POINT scr_save_mouse_pos;
179 static LRESULT CALLBACK screenSaverWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
188 LONG dx = scr_save_mouse_pos.x - pt.x;
189 LONG dy = scr_save_mouse_pos.y - pt.y;
190 if (abs(dx) > SCR_SAVE_MOUSE_MOVE_THRESHOLD
191 || abs(dy) > SCR_SAVE_MOUSE_MOVE_THRESHOLD)
195 scr_save_mouse_pos = pt;
205 PostMessage(hwnd,WM_CLOSE,0,0);
206 return CallWindowProc(ghost_wnd_proc, hwnd, uMsg, wParam, lParam);
209 BOOL CALLBACK findGhostWindowHWNDProc(HWND hwnd, LPARAM lParam)
211 GHOST_IWindow *p = (GHOST_IWindow*) GetWindowLong(hwnd, GWL_USERDATA);
213 if (p == ghost_window_to_find)
215 found_ghost_window_hwnd = hwnd;
221 static HWND findGhostWindowHWND(GHOST_IWindow* window)
223 found_ghost_window_hwnd = NULL;
224 ghost_window_to_find = window;
225 EnumWindows(findGhostWindowHWNDProc, NULL);
226 return found_ghost_window_hwnd;
229 bool GPG_Application::startScreenSaverPreview(
231 const bool stereoVisual,
232 const int stereoMode)
234 bool success = false;
237 if (GetWindowRect(parentWindow, &rc))
239 int windowWidth = rc.right - rc.left;
240 int windowHeight = rc.bottom - rc.top;
241 STR_String title = "";
243 m_mainWindow = fSystem->createWindow(title, 0, 0, windowWidth, windowHeight, GHOST_kWindowStateMinimized,
244 GHOST_kDrawingContextTypeOpenGL, stereoVisual);
246 printf("error: could not create main window\n");
250 HWND ghost_hwnd = findGhostWindowHWND(m_mainWindow);
252 printf("error: could find main window\n");
256 SetParent(ghost_hwnd, parentWindow);
257 LONG style = GetWindowLong(ghost_hwnd, GWL_STYLE);
258 LONG exstyle = GetWindowLong(ghost_hwnd, GWL_EXSTYLE);
260 RECT adjrc = { 0, 0, windowWidth, windowHeight };
261 AdjustWindowRectEx(&adjrc, style, FALSE, exstyle);
263 style = (style & (~(WS_POPUP|WS_OVERLAPPEDWINDOW|WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_TILEDWINDOW ))) | WS_CHILD;
264 SetWindowLong(ghost_hwnd, GWL_STYLE, style);
265 SetWindowPos(ghost_hwnd, NULL, adjrc.left, adjrc.top, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOACTIVATE);
267 /* Check the size of the client rectangle of the window and resize the window
268 * so that the client rectangle has the size requested.
270 m_mainWindow->setClientSize(windowWidth, windowHeight);
272 success = initEngine(m_mainWindow, stereoMode);
274 success = startEngine();
281 bool GPG_Application::startScreenSaverFullScreen(
284 int bpp,int frequency,
285 const bool stereoVisual,
286 const int stereoMode)
288 bool ret = startFullScreen(width, height, bpp, frequency, stereoVisual, stereoMode);
291 HWND ghost_hwnd = findGhostWindowHWND(m_mainWindow);
292 if (ghost_hwnd != NULL)
294 GetCursorPos(&scr_save_mouse_pos);
295 ghost_wnd_proc = (WNDPROC) GetWindowLong(ghost_hwnd, GWL_WNDPROC);
296 SetWindowLong(ghost_hwnd,GWL_WNDPROC, (LONG) screenSaverWindowProc);
304 bool GPG_Application::startWindow(STR_String& title,
309 const bool stereoVisual,
310 const int stereoMode)
313 // Create the main window
314 //STR_String title ("Blender Player - GHOST");
315 m_mainWindow = fSystem->createWindow(title, windowLeft, windowTop, windowWidth, windowHeight, GHOST_kWindowStateNormal,
316 GHOST_kDrawingContextTypeOpenGL, stereoVisual);
318 printf("error: could not create main window\n");
322 /* Check the size of the client rectangle of the window and resize the window
323 * so that the client rectangle has the size requested.
325 m_mainWindow->setClientSize(windowWidth, windowHeight);
326 m_mainWindow->setCursorVisibility(false);
328 success = initEngine(m_mainWindow, stereoMode);
330 success = startEngine();
335 bool GPG_Application::startEmbeddedWindow(STR_String& title,
336 const GHOST_TEmbedderWindowID parentWindow,
337 const bool stereoVisual,
338 const int stereoMode) {
340 m_mainWindow = fSystem->createWindow(title, 0, 0, 0, 0, GHOST_kWindowStateNormal,
341 GHOST_kDrawingContextTypeOpenGL, stereoVisual, parentWindow);
344 printf("error: could not create main window\n");
349 bool success = initEngine(m_mainWindow, stereoMode);
351 success = startEngine();
357 bool GPG_Application::startFullScreen(
360 int bpp,int frequency,
361 const bool stereoVisual,
362 const int stereoMode)
365 // Create the main window
366 GHOST_DisplaySetting setting;
367 setting.xPixels = width;
368 setting.yPixels = height;
370 setting.frequency = frequency;
372 fSystem->beginFullScreen(setting, &m_mainWindow, stereoVisual);
373 m_mainWindow->setCursorVisibility(false);
375 success = initEngine(m_mainWindow, stereoMode);
377 success = startEngine();
385 bool GPG_Application::StartGameEngine(int stereoMode)
387 bool success = initEngine(m_mainWindow, stereoMode);
390 success = startEngine();
397 void GPG_Application::StopGameEngine()
404 bool GPG_Application::processEvent(GHOST_IEvent* event)
408 switch (event->getType())
410 case GHOST_kEventUnknown:
413 case GHOST_kEventButtonDown:
414 handled = handleButton(event, true);
417 case GHOST_kEventButtonUp:
418 handled = handleButton(event, false);
421 case GHOST_kEventWheel:
422 handled = handleWheel(event);
425 case GHOST_kEventCursorMove:
426 handled = handleCursorMove(event);
429 case GHOST_kEventKeyDown:
430 handleKey(event, true);
433 case GHOST_kEventKeyUp:
434 handleKey(event, false);
438 case GHOST_kEventWindowClose:
439 m_exitRequested = KX_EXIT_REQUEST_OUTSIDE;
442 case GHOST_kEventWindowActivate:
445 case GHOST_kEventWindowDeactivate:
449 case GHOST_kEventWindowUpdate:
451 GHOST_IWindow* window = event->getWindow();
452 if (!m_system->validWindow(window)) break;
453 // Update the state of the game engine
454 if (m_kxsystem && !m_exitRequested)
456 // Proceed to next frame
457 window->activateDrawingContext();
459 // first check if we want to exit
460 m_exitRequested = m_ketsjiengine->GetExitCode();
463 bool renderFrame = m_ketsjiengine->NextFrame();
467 m_ketsjiengine->Render();
470 m_exitString = m_ketsjiengine->GetExitString();
474 case GHOST_kEventWindowSize:
476 GHOST_IWindow* window = event->getWindow();
477 if (!m_system->validWindow(window)) break;
480 window->getClientBounds(bnds);
481 m_canvas->Resize(bnds.getWidth(), bnds.getHeight());
495 int GPG_Application::getExitRequested(void)
497 return m_exitRequested;
502 const STR_String& GPG_Application::getExitString(void)
509 bool GPG_Application::initEngine(GHOST_IWindow* window, const int stereoMode)
511 if (!m_engineInitialized)
513 GPU_extensions_init();
514 bgl::InitExtensions(true);
516 // get and set the preferences
517 SYS_SystemHandle syshandle = SYS_GetSystem();
521 // SYS_WriteCommandLineInt(syshandle, "fixedtime", 0);
522 // SYS_WriteCommandLineInt(syshandle, "vertexarrays",1);
523 bool properties = (SYS_GetCommandLineInt(syshandle, "show_properties", 0) != 0);
524 bool profile = (SYS_GetCommandLineInt(syshandle, "show_profile", 0) != 0);
525 bool fixedFr = (G.fileflags & G_FILE_ENABLE_ALL_FRAMES);
527 bool showPhysics = (G.fileflags & G_FILE_SHOW_PHYSICS);
528 SYS_WriteCommandLineInt(syshandle, "show_physics", showPhysics);
530 bool fixed_framerate= (SYS_GetCommandLineInt(syshandle, "fixed_framerate", fixedFr) != 0);
531 bool frameRate = (SYS_GetCommandLineInt(syshandle, "show_framerate", 0) != 0);
532 bool useLists = (SYS_GetCommandLineInt(syshandle, "displaylists", G.fileflags & G_FILE_DISPLAY_LISTS) != 0);
533 bool nodepwarnings = (SYS_GetCommandLineInt(syshandle, "ignore_deprecation_warnings", 1) != 0);
535 if(GLEW_ARB_multitexture && GLEW_VERSION_1_1)
536 m_blendermat = (SYS_GetCommandLineInt(syshandle, "blender_material", 1) != 0);
538 if(GPU_extensions_minimum_support())
539 m_blenderglslmat = (SYS_GetCommandLineInt(syshandle, "blender_glsl_material", 1) != 0);
540 else if(G.fileflags & G_FILE_GAME_MAT_GLSL)
541 m_blendermat = false;
543 // create the canvas, rasterizer and rendertools
544 m_canvas = new GPG_Canvas(window);
549 m_rendertools = new GPC_RenderTools();
555 m_rasterizer = new RAS_ListRasterizer(m_canvas, true);
557 m_rasterizer = new RAS_ListRasterizer(m_canvas);
559 else if (GLEW_VERSION_1_1)
560 m_rasterizer = new RAS_VAOpenGLRasterizer(m_canvas);
562 m_rasterizer = new RAS_OpenGLRasterizer(m_canvas);
564 m_rasterizer->SetStereoMode((RAS_IRasterizer::StereoMode) stereoMode);
568 // create the inputdevices
569 m_keyboard = new GPG_KeyboardDevice();
573 m_mouse = new GPC_MouseDevice();
577 // create a networkdevice
578 m_networkdevice = new NG_LoopBackNetworkDeviceInterface();
579 if (!m_networkdevice)
582 // get an audiodevice
583 SND_DeviceManager::Subscribe();
584 m_audiodevice = SND_DeviceManager::Instance();
587 m_audiodevice->UseCD();
589 // create a ketsjisystem (only needed for timing and stuff)
590 m_kxsystem = new GPG_System (m_system);
594 // create the ketsjiengine
595 m_ketsjiengine = new KX_KetsjiEngine(m_kxsystem);
598 m_ketsjiengine->SetKeyboardDevice(m_keyboard);
599 m_ketsjiengine->SetMouseDevice(m_mouse);
600 m_ketsjiengine->SetNetworkDevice(m_networkdevice);
601 m_ketsjiengine->SetCanvas(m_canvas);
602 m_ketsjiengine->SetRenderTools(m_rendertools);
603 m_ketsjiengine->SetRasterizer(m_rasterizer);
604 m_ketsjiengine->SetNetworkDevice(m_networkdevice);
605 m_ketsjiengine->SetAudioDevice(m_audiodevice);
606 m_ketsjiengine->SetTimingDisplay(frameRate, false, false);
608 CValue::SetDeprecationWarnings(nodepwarnings);
610 m_ketsjiengine->SetUseFixedTime(fixed_framerate);
611 m_ketsjiengine->SetTimingDisplay(frameRate, profile, properties);
613 m_engineInitialized = true;
616 return m_engineInitialized;
619 delete m_audiodevice;
620 delete m_networkdevice;
624 delete m_rendertools;
627 m_rendertools = NULL;
631 m_networkdevice = NULL;
632 m_audiodevice = NULL;
639 bool GPG_Application::startEngine(void)
641 if (m_engineRunning) {
645 // Temporary hack to disable banner display for NaN approved content.
647 m_canvas->SetBannerDisplayEnabled(true);
649 cam = (Camera*)G.scene->camera->data;
651 if (((cam->flag) & 48)==48) {
652 m_canvas->SetBannerDisplayEnabled(false);
656 showError(CString("Camera data invalid."));
661 // create a scene converter, create and convert the stratingscene
662 m_sceneconverter = new KX_BlenderSceneConverter(m_maggie,0, m_ketsjiengine);
663 if (m_sceneconverter)
665 STR_String startscenename = m_startSceneName.Ptr();
666 m_ketsjiengine->SetSceneConverter(m_sceneconverter);
668 // if (always_use_expand_framing)
669 // sceneconverter->SetAlwaysUseExpandFraming(true);
670 if(m_blendermat && (G.fileflags & G_FILE_GAME_MAT))
671 m_sceneconverter->SetMaterials(true);
672 if(m_blenderglslmat && (G.fileflags & G_FILE_GAME_MAT_GLSL))
673 m_sceneconverter->SetGLSLMaterials(true);
675 KX_Scene* startscene = new KX_Scene(m_keyboard,
683 // some python things
684 PyObject* dictionaryobject = initGamePlayerPythonScripting("Ketsji", psl_Lowest, m_maggie);
685 m_ketsjiengine->SetPythonDictionary(dictionaryobject);
686 initRasterizer(m_rasterizer, m_canvas);
687 PyObject *gameLogic = initGameLogic(m_ketsjiengine, startscene);
688 PyDict_SetItemString(dictionaryobject, "GameLogic", gameLogic); // Same as importing the module
690 initPythonConstraintBinding();
696 // Set the GameLogic.globalDict from marshal'd data, so we can
697 // load new blend files and keep data in GameLogic.globalDict
698 loadGamePythonConfig(m_pyGlobalDictString, m_pyGlobalDictString_Length);
700 m_sceneconverter->ConvertScene(
707 m_ketsjiengine->AddScene(startscene);
709 // Create a timer that is used to kick the engine
711 m_frameTimer = m_system->installTimer(0, kTimerFreq, frameTimerProc, m_mainWindow);
713 m_rasterizer->Init();
714 m_ketsjiengine->StartEngine(true);
715 m_engineRunning = true;
717 // Set the animation playback rate for ipo's and actions
718 // the framerate below should patch with FPS macro defined in blendef.h
719 // Could be in StartEngine set the framerate, we need the scene to do this
720 m_ketsjiengine->SetAnimFrameRate( (((double) G.scene->r.frs_sec) / G.scene->r.frs_sec_base) );
724 if (!m_engineRunning)
729 return m_engineRunning;
733 void GPG_Application::stopEngine()
735 // GameLogic.globalDict gets converted into a buffer, and sorted in
736 // m_pyGlobalDictString so we can restore after python has stopped
737 // and started between .blend file loads.
738 if(m_pyGlobalDictString) {
739 delete [] m_pyGlobalDictString;
740 m_pyGlobalDictString = 0;
743 m_pyGlobalDictString_Length = saveGamePythonConfig(&m_pyGlobalDictString);
745 // when exiting the mainloop
746 exitGamePythonScripting();
747 m_ketsjiengine->StopEngine();
748 m_networkdevice->Disconnect();
750 if (m_sceneconverter) {
751 delete m_sceneconverter;
752 m_sceneconverter = 0;
754 if (m_system && m_frameTimer) {
755 m_system->removeTimer(m_frameTimer);
758 m_engineRunning = false;
762 void GPG_Application::exitEngine()
767 delete m_ketsjiengine;
777 SND_DeviceManager::Unsubscribe();
782 delete m_networkdevice;
802 delete m_rendertools;
812 #ifdef WITH_QUICKTIME
815 GPU_extensions_exit();
818 m_engineInitialized = false;
821 bool GPG_Application::handleWheel(GHOST_IEvent* event)
823 bool handled = false;
827 GHOST_TEventDataPtr eventData = ((GHOST_IEvent*)event)->getData();
828 GHOST_TEventWheelData* wheelData = static_cast<GHOST_TEventWheelData*>(eventData);
829 GPC_MouseDevice::TButtonId button;
830 if (wheelData->z > 0)
831 button = GPC_MouseDevice::buttonWheelUp;
833 button = GPC_MouseDevice::buttonWheelDown;
834 m_mouse->ConvertButtonEvent(button, true);
840 bool GPG_Application::handleButton(GHOST_IEvent* event, bool isDown)
842 bool handled = false;
846 GHOST_TEventDataPtr eventData = ((GHOST_IEvent*)event)->getData();
847 GHOST_TEventButtonData* buttonData = static_cast<GHOST_TEventButtonData*>(eventData);
848 GPC_MouseDevice::TButtonId button;
849 switch (buttonData->button)
851 case GHOST_kButtonMaskMiddle:
852 button = GPC_MouseDevice::buttonMiddle;
854 case GHOST_kButtonMaskRight:
855 button = GPC_MouseDevice::buttonRight;
857 case GHOST_kButtonMaskLeft:
859 button = GPC_MouseDevice::buttonLeft;
862 m_mouse->ConvertButtonEvent(button, isDown);
869 bool GPG_Application::handleCursorMove(GHOST_IEvent* event)
871 bool handled = false;
873 if (m_mouse && m_mainWindow)
875 GHOST_TEventDataPtr eventData = ((GHOST_IEvent*)event)->getData();
876 GHOST_TEventCursorData* cursorData = static_cast<GHOST_TEventCursorData*>(eventData);
878 m_mainWindow->screenToClient(cursorData->x, cursorData->y, x, y);
879 m_mouse->ConvertMoveEvent(x, y);
886 bool GPG_Application::handleKey(GHOST_IEvent* event, bool isDown)
888 bool handled = false;
892 GHOST_TEventDataPtr eventData = ((GHOST_IEvent*)event)->getData();
893 GHOST_TEventKeyData* keyData = static_cast<GHOST_TEventKeyData*>(eventData);
894 //no need for this test
895 //if (fSystem->getFullScreen()) {
896 if (keyData->key == GHOST_kKeyEsc && !m_keyboard->m_hookesc && !m_isEmbedded) {
897 m_exitRequested = KX_EXIT_REQUEST_OUTSIDE;
900 m_keyboard->ConvertEvent(keyData->key, isDown);
908 static void frameTimerProc(GHOST_ITimerTask* task, GHOST_TUns64 time)
910 GHOST_IWindow* window = (GHOST_IWindow*)task->getUserData();
911 if (fSystem->validWindow(window)) {
912 window->invalidate();