From 874d63f5bcbb222b1452997d5e5fab1cf465f82e Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Thu, 29 Jan 2015 18:00:33 +0100 Subject: [PATCH 001/378] WIP wayland --- CMakeLists.txt | 2 +- lib/glew/src/glew.c | 4 +- lib/irrlicht/CMakeLists.txt | 2 + lib/irrlicht/include/EDeviceTypes.h | 2 + lib/irrlicht/include/IrrCompileConfig.h | 2 + .../source/Irrlicht/COpenGLDriver.cpp | 80 ++ lib/irrlicht/source/Irrlicht/COpenGLDriver.h | 11 + lib/irrlicht/source/Irrlicht/Irrlicht.cpp | 9 + .../source/Irrlicht/cirrdevicewayland.cpp | 1206 +++++++++++++++++ .../source/Irrlicht/cirrdevicewayland.h | 309 +++++ src/graphics/irr_driver.cpp | 11 +- 11 files changed, 1629 insertions(+), 9 deletions(-) create mode 100644 lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp create mode 100644 lib/irrlicht/source/Irrlicht/cirrdevicewayland.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9aa3fe402..f29517102 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,7 +175,7 @@ endif() if(UNIX OR MINGW) # if(USE_CPP2011) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x -lwayland-client -lwayland-egl -lEGL -lxkbcommon") # endif() endif() diff --git a/lib/glew/src/glew.c b/lib/glew/src/glew.c index dd780bf49..0e035f132 100644 --- a/lib/glew/src/glew.c +++ b/lib/glew/src/glew.c @@ -14065,7 +14065,7 @@ GLenum glxewContextInit (GLXEW_CONTEXT_ARG_DEF_LIST) GLXEW_VERSION_1_3 = GL_TRUE; GLXEW_VERSION_1_4 = GL_TRUE; /* query GLX version */ - glXQueryVersion(glXGetCurrentDisplay(), &major, &minor); +/* glXQueryVersion(glXGetCurrentDisplay(), &major, &minor); if (major == 1 && minor <= 3) { switch (minor) @@ -14081,7 +14081,7 @@ GLenum glxewContextInit (GLXEW_CONTEXT_ARG_DEF_LIST) return GLEW_ERROR_GLX_VERSION_11_ONLY; break; } - } + }*/ /* query GLX extension string */ extStart = 0; if (glXGetCurrentDisplay != NULL) diff --git a/lib/irrlicht/CMakeLists.txt b/lib/irrlicht/CMakeLists.txt index 9c9429dc1..6b130a034 100644 --- a/lib/irrlicht/CMakeLists.txt +++ b/lib/irrlicht/CMakeLists.txt @@ -91,6 +91,7 @@ source/Irrlicht/CTriangleSelector.cpp source/Irrlicht/CParticlePointEmitter.cpp source/Irrlicht/CTextSceneNode.cpp source/Irrlicht/CIrrDeviceLinux.cpp +source/Irrlicht/cirrdevicewayland.cpp source/Irrlicht/CIrrDeviceStub.cpp source/Irrlicht/CGUIInOutFader.cpp source/Irrlicht/CGUITreeView.cpp @@ -160,6 +161,7 @@ source/Irrlicht/CSceneNodeAnimatorRotation.cpp source/Irrlicht/glext.h source/Irrlicht/CB3DMeshFileLoader.h source/Irrlicht/CIrrDeviceLinux.h +source/Irrlicht/cirrdevicewayland.h source/Irrlicht/CMeshCache.h source/Irrlicht/CAttributes.h source/Irrlicht/CParticleMeshEmitter.h diff --git a/lib/irrlicht/include/EDeviceTypes.h b/lib/irrlicht/include/EDeviceTypes.h index 44b009431..d0d270e7b 100644 --- a/lib/irrlicht/include/EDeviceTypes.h +++ b/lib/irrlicht/include/EDeviceTypes.h @@ -20,6 +20,8 @@ namespace irr /** This device works on Windows Mobile, Pocket PC and Microsoft SmartPhone devices */ EIDT_WINCE, + EIDT_WAYLAND, + //! A device native to Unix style operating systems. /** This device uses the X11 windowing system and works in Linux, Solaris, FreeBSD, OSX and other operating systems which support X11. */ diff --git a/lib/irrlicht/include/IrrCompileConfig.h b/lib/irrlicht/include/IrrCompileConfig.h index 10a1bc279..87780ec21 100644 --- a/lib/irrlicht/include/IrrCompileConfig.h +++ b/lib/irrlicht/include/IrrCompileConfig.h @@ -164,6 +164,8 @@ define out. */ #undef _IRR_COMPILE_WITH_X11_ #endif +#define _IRR_COMPILE_WITH_WAYLAND + //! Define _IRR_OPENGL_USE_EXTPOINTER_ if the OpenGL renderer should use OpenGL extensions via function pointers. /** On some systems there is no support for the dynamic extension of OpenGL via function pointers such that this has to be undef'ed. */ diff --git a/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp b/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp index 79dd45682..c2504855b 100644 --- a/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp +++ b/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp @@ -25,6 +25,10 @@ extern bool GLContextDebugBit; #include "MacOSX/CIrrDeviceMacOSX.h" #endif +#ifdef _IRR_COMPILE_WITH_WAYLAND +#include "cirrdevicewayland.h" +#endif + namespace irr { namespace video @@ -673,6 +677,53 @@ bool COpenGLDriver::initDriver(CIrrDeviceLinux* device) #endif // _IRR_COMPILE_WITH_X11_DEVICE_ +// ----------------------------------------------------------------------- +// Wayland CONSTRUCTOR +// ----------------------------------------------------------------------- +#ifdef _IRR_COMPILE_WITH_WAYLAND +//! Linux constructor and init code +COpenGLDriver::COpenGLDriver(const SIrrlichtCreationParameters& params, + io::IFileSystem* io, CIrrDeviceWayland* device) +: CNullDriver(io, params.WindowSize), COpenGLExtensionHandler(), + CurrentRenderMode(ERM_NONE), ResetRenderStates(true), + Transformation3DChanged(true), AntiAlias(params.AntiAlias), + RenderTargetTexture(0), CurrentRendertargetSize(0, 0), ColorFormat(ECF_R8G8B8), + CurrentTarget(ERT_FRAME_BUFFER), Params(params), + wl_device(device), DeviceType(EIDT_WAYLAND) +{ + #ifdef _DEBUG + setDebugName("COpenGLDriver"); + #endif + genericDriverInit(); +} + + +bool COpenGLDriver::changeRenderContext(const SExposedVideoData& videoData, CIrrDeviceWayland* device) +{ + return true; +} + + +//! inits the open gl driver +bool COpenGLDriver::initDriver(CIrrDeviceWayland* device) +{ +/* ExposedData.OpenGLLinux.X11Context = glXGetCurrentContext(); + ExposedData.OpenGLLinux.X11Display = glXGetCurrentDisplay(); + ExposedData.OpenGLLinux.X11Window = (unsigned long)Params.WindowId; + Drawable = glXGetCurrentDrawable(); + X11Display = (Display*)ExposedData.OpenGLLinux.X11Display;*/ + + genericDriverInit(); + + // set vsync +// extGlSwapInterval(Params.Vsync ? 1 : 0); + return true; +} + +#endif // _IRR_COMPILE_WITH_X11_DEVICE_ + + + // ----------------------------------------------------------------------- // SDL CONSTRUCTOR // ----------------------------------------------------------------------- @@ -914,6 +965,16 @@ bool COpenGLDriver::endScene() } #endif +#ifdef _IRR_COMPILE_WITH_X11_DEVICE_ + if (DeviceType == EIDT_WAYLAND) + { + wl_display_dispatch_pending(wl_device->display); + eglSwapBuffers(wl_device->egl_display, wl_device->egl_surface); + + return true; + } +#endif + #ifdef _IRR_COMPILE_WITH_OSX_DEVICE_ if (DeviceType == EIDT_OSX) { @@ -4900,6 +4961,25 @@ IVideoDriver* createOpenGLDriver(const SIrrlichtCreationParameters& params, #endif // _IRR_COMPILE_WITH_X11_DEVICE_ +// ----------------------------------- +// Wayland VERSION +// ----------------------------------- +#ifdef _IRR_COMPILE_WITH_WAYLAND +IVideoDriver* createOpenGLDriver(const SIrrlichtCreationParameters& params, + io::IFileSystem* io, CIrrDeviceWayland* device) +{ + COpenGLDriver* ogl = new COpenGLDriver(params, io, device); + if (!ogl->initDriver(device)) + { + ogl->drop(); + ogl = 0; + } + return ogl; + +} +#endif // _IRR_COMPILE_WITH_WAYLAND + + // ----------------------------------- // SDL VERSION // ----------------------------------- diff --git a/lib/irrlicht/source/Irrlicht/COpenGLDriver.h b/lib/irrlicht/source/Irrlicht/COpenGLDriver.h index 1f3c2c152..178c96584 100644 --- a/lib/irrlicht/source/Irrlicht/COpenGLDriver.h +++ b/lib/irrlicht/source/Irrlicht/COpenGLDriver.h @@ -13,6 +13,7 @@ namespace irr { class CIrrDeviceWin32; class CIrrDeviceLinux; + class CIrrDeviceWayland; class CIrrDeviceSDL; class CIrrDeviceMacOSX; } @@ -44,6 +45,13 @@ namespace video bool changeRenderContext(const SExposedVideoData& videoData, CIrrDeviceWin32* device); #endif + #ifdef _IRR_COMPILE_WITH_WAYLAND + COpenGLDriver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, CIrrDeviceWayland* device); + //! inits the EGL specific parts of the open gl driver + bool initDriver(CIrrDeviceWayland* device); + bool changeRenderContext(const SExposedVideoData& videoData, CIrrDeviceWayland* device); + #endif + #ifdef _IRR_COMPILE_WITH_X11_DEVICE_ COpenGLDriver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, CIrrDeviceLinux* device); //! inits the GLX specific parts of the open gl driver @@ -585,6 +593,9 @@ namespace video Display* X11Display; CIrrDeviceLinux *X11Device; #endif + #ifdef _IRR_COMPILE_WITH_WAYLAND + CIrrDeviceWayland *wl_device; + #endif #ifdef _IRR_COMPILE_WITH_OSX_DEVICE_ CIrrDeviceMacOSX *OSXDevice; #endif diff --git a/lib/irrlicht/source/Irrlicht/Irrlicht.cpp b/lib/irrlicht/source/Irrlicht/Irrlicht.cpp index 9539a8bd5..2c9df1651 100644 --- a/lib/irrlicht/source/Irrlicht/Irrlicht.cpp +++ b/lib/irrlicht/source/Irrlicht/Irrlicht.cpp @@ -26,6 +26,10 @@ static const char* const copyright = "Irrlicht Engine (c) 2002-2012 Nikolaus Geb #include "CIrrDeviceWinCE.h" #endif +#ifdef _IRR_COMPILE_WITH_WAYLAND +#include "cirrdevicewayland.h" +#endif + #ifdef _IRR_COMPILE_WITH_X11_DEVICE_ #include "CIrrDeviceLinux.h" #endif @@ -84,6 +88,11 @@ namespace irr dev = new CIrrDeviceWinCE(params); #endif +#ifdef _IRR_COMPILE_WITH_WAYLAND + if (params.DeviceType == EIDT_WAYLAND || (!dev && params.DeviceType == EIDT_BEST)) + dev = new CIrrDeviceWayland(params); +#endif + #ifdef _IRR_COMPILE_WITH_X11_DEVICE_ if (params.DeviceType == EIDT_X11 || (!dev && params.DeviceType == EIDT_BEST)) dev = new CIrrDeviceLinux(params); diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp new file mode 100644 index 000000000..c7f78bffa --- /dev/null +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp @@ -0,0 +1,1206 @@ +extern bool GLContextDebugBit; + +#include "cirrdevicewayland.h" + + +#include +#include +#include +#include +#include "IEventReceiver.h" +#include "ISceneManager.h" +#include "IGUIEnvironment.h" +#include "os.h" +#include "CTimer.h" +#include "irrString.h" +#include "Keycodes.h" +#include "COSOperator.h" +#include "CColorConverter.h" +#include "SIrrCreationParameters.h" +#include "IGUISpriteBank.h" +#include +#include "CVideoModeList.h" + +#if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ +#include +#include + + +// linux/joystick.h includes linux/input.h, which #defines values for various KEY_FOO keys. +// These override the irr::KEY_FOO equivalents, which stops key handling from working. +// As a workaround, defining _INPUT_H stops linux/input.h from being included; it +// doesn't actually seem to be necessary except to pull in sys/ioctl.h. +#define _INPUT_H +#include // Would normally be included in linux/input.h +#include +#undef _INPUT_H + +#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ + +#define XRANDR_ROTATION_LEFT (1 << 1) +#define XRANDR_ROTATION_RIGHT (1 << 3) + +namespace irr +{ + namespace video + { + extern bool useCoreContext; + IVideoDriver* createOpenGLDriver(const SIrrlichtCreationParameters& params, + io::IFileSystem* io, CIrrDeviceWayland* device); + } +} // end namespace irr + +namespace irr +{ + +class WaylandCallbacks +{ +public: + // from http://cgit.freedesktop.org/wayland/weston/tree/clients/simple-egl.c + static void + pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) + { + printf("enter!\n"); + } + + static void + pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) + { + } + + static void + pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) + { + CIrrDeviceWayland *dev = static_cast(data); + dev->getCursorControl()->setPosition(sx, sy); + } + + static void + pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) + { + CIrrDeviceWayland *dev = static_cast(data); + + SEvent irrevent; + irrevent.MouseInput.ButtonStates = 0xffffffff; + + irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.X = dev->getCursorControl()->getPosition().X; + irrevent.MouseInput.Y = dev->getCursorControl()->getPosition().Y; + // irrevent.MouseInput.Control = (event.xkey.state & ControlMask) != 0; + // irrevent.MouseInput.Shift = (event.xkey.state & ShiftMask) != 0; + + // mouse button states + // This sets the state which the buttons had _prior_ to the event. + // So unlike on Windows the button which just got changed has still the old state here. + // We handle that below by flipping the corresponding bit later. + // irrevent.MouseInput.ButtonStates = (event.xbutton.state & Button1Mask) ? irr::EMBSM_LEFT : 0; + // irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button3Mask) ? irr::EMBSM_RIGHT : 0; + // irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button2Mask) ? irr::EMBSM_MIDDLE : 0; + + irrevent.MouseInput.Event = irr::EMIE_COUNT; + + switch(button) + { + case 272: // Left button + irrevent.MouseInput.Event = + (state == WL_POINTER_BUTTON_STATE_PRESSED) ? irr::EMIE_LMOUSE_PRESSED_DOWN : irr::EMIE_LMOUSE_LEFT_UP; + irrevent.MouseInput.ButtonStates ^= irr::EMBSM_LEFT; + break; + default: + break; + } + dev->signalEvent(irrevent); + } + + static void + pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) + { + } + + static const struct wl_pointer_listener pointer_listener; + + static void + keyboard_repeat_func(struct task *task, uint32_t events) + { + } + + static void + keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) + { + printf("initiating mmaped keymap\n"); + CIrrDeviceWayland *device = static_cast(data); + char *map_str; + + if (!data) { + close(fd); + return; + } + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + map_str = static_cast(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0)); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + device->keymap = xkb_keymap_new_from_string(device->xkbctx, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_str, size); + close(fd); + + if (!device->keymap) { + fprintf(stderr, "failed to compile keymap\n"); + return; + } + + device->state = xkb_state_new(device->keymap); + if (!device->state) { + fprintf(stderr, "failed to create XKB state\n"); + xkb_keymap_unref(device->keymap); + return; + } + + // xkb_keymap_unref(device->keymap); + // xkb_state_unref(device->state); + // device->keymap = keymap; + // device->state = state; + + /* input->xkb.control_mask = + 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Control"); + input->xkb.alt_mask = + 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Mod1"); + input->xkb.shift_mask = + 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Shift");*/ + } + + static void + keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) + { + } + + static void + keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) + { + } + + static void + keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state_w) + { + CIrrDeviceWayland *device = static_cast(data); + uint32_t code; + enum wl_keyboard_key_state state = (wl_keyboard_key_state) state_w; + xkb_keysym_t sym; + SEvent irrevent; + + code = key + 8; + sym = xkb_state_key_get_one_sym(device->state, code); + + CIrrDeviceWayland::SKeyMap mp; + mp.X11Key = sym; + + irrevent.EventType = irr::EET_KEY_INPUT_EVENT; + irrevent.KeyInput.PressedDown = (state == WL_KEYBOARD_KEY_STATE_PRESSED); + // irrevent.KeyInput.Char = ((wchar_t*)(buf))[0]; + // irrevent.KeyInput.Control = (event.xkey.state & ControlMask) != 0; + // irrevent.KeyInput.Shift = (event.xkey.state & ShiftMask) != 0; + + const s32 idx = device->KeyMap.binary_search(mp); + if (idx != -1) + { + irrevent.KeyInput.Key = (EKEY_CODE)device->KeyMap[idx].Win32Key; + } + else + { + irrevent.KeyInput.Key = (EKEY_CODE)0; + } + if (irrevent.KeyInput.Key == 0) + { + // 1:1 mapping to windows-keys would require testing for keyboard type (us, ger, ...) + // So unless we do that we will have some unknown keys here. + if (idx == -1) + { + // os::Printer::log("Could not find EKEY_CODE, using orig. X11 keycode instead", core::stringc(event.xkey.keycode).c_str(), ELL_INFORMATION); + } + else + { + // os::Printer::log("EKEY_CODE is 0, using orig. X11 keycode instead", core::stringc(event.xkey.keycode).c_str(), ELL_INFORMATION); + } + // Any value is better than none, that allows at least using the keys. + // Worst case is that some keys will be identical, still better than _all_ + // unknown keys being identical. + // irrevent.KeyInput.Key = (EKEY_CODE)event.xkey.keycode; + } + + device->signalEvent(irrevent); + } + + static void + keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) + { + /* struct input *input = data; + xkb_mod_mask_t mask;*/ + + /* If we're not using a keymap, then we don't handle PC-style modifiers */ + /* if (!input->xkb.keymap) + return; + + xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); + mask = xkb_state_serialize_mods(input->xkb.state, + XKB_STATE_MODS_DEPRESSED | + XKB_STATE_MODS_LATCHED); + input->modifiers = 0; + if (mask & input->xkb.control_mask) + input->modifiers |= MOD_CONTROL_MASK; + if (mask & input->xkb.alt_mask) + input->modifiers |= MOD_ALT_MASK; + if (mask & input->xkb.shift_mask) + input->modifiers |= MOD_SHIFT_MASK;*/ + } + + static void + set_repeat_info(struct input *input, int32_t rate, int32_t delay) + { + + } + + static void + keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, + int32_t rate, int32_t delay) + { + } + + static const struct wl_keyboard_listener keyboard_listener; + + static void + seat_handle_capabilities(void *data, struct wl_seat *seat, + uint32_t caps) + { + CIrrDeviceWayland *dev = static_cast(data); + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !dev->pointer) { + dev->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(dev->pointer, &pointer_listener, data); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && dev->pointer) { + wl_pointer_destroy(dev->pointer); + dev->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !dev->keyboard) { + dev->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(dev->keyboard, &keyboard_listener, dev); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && dev->keyboard) { + wl_keyboard_destroy(dev->keyboard); + dev->keyboard = NULL; + } + } + + static const struct wl_seat_listener seat_listener; + + static void + display_handle_geometry(void *data, + struct wl_output *wl_output, + int x, int y, + int physical_width, + int physical_height, + int subpixel, + const char *make, + const char *model, + int transform) + { + printf("output model is %s\n", model); + } + + static void + display_handle_done(void *data, + struct wl_output *wl_output) + { + printf("done\n"); + } + + static void + display_handle_scale(void *data, + struct wl_output *wl_output, + int32_t scale) + { + } + + static void + display_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int refresh) + { + CIrrDeviceWayland *dev = static_cast(data); + dev->VideoModeList.addMode(core::dimension2du(width, height), 24); + if (flags & WL_OUTPUT_MODE_CURRENT) { + dev->VideoModeList.setDesktop(24, core::dimension2du(width, height)); + } + } + + static const struct wl_output_listener output_listener; + + static void registry_add (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + CIrrDeviceWayland *dev = static_cast(data); + if (!strcmp(interface,"wl_compositor")) { + printf("binding compositor\n"); + dev->compositor = static_cast(wl_registry_bind (registry, name, &wl_compositor_interface, 0)); + } + else if (!strcmp(interface,"wl_shell")) { + printf("binding shell\n"); + dev->shell = static_cast(wl_registry_bind (registry, name, &wl_shell_interface, 0)); + } + else if (strcmp(interface, "wl_seat") == 0) { + printf("binding seat\n"); + dev->seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, 0)); + } + else if (strcmp(interface, "wl_output") == 0) + { + printf("binding output\n"); + dev->output = static_cast(wl_registry_bind(dev->registry, name, &wl_output_interface, 2)); + } + } + + static void registry_remove (void *data, struct wl_registry *registry, uint32_t name) { + printf ("registry_remove_object\n"); + } + + static const wl_registry_listener registry_listener; +}; + +const struct wl_pointer_listener WaylandCallbacks::pointer_listener = { + WaylandCallbacks::pointer_handle_enter, + WaylandCallbacks::pointer_handle_leave, + WaylandCallbacks::pointer_handle_motion, + WaylandCallbacks::pointer_handle_button, + WaylandCallbacks::pointer_handle_axis, +}; + +const struct wl_keyboard_listener WaylandCallbacks::keyboard_listener = { + WaylandCallbacks::keyboard_handle_keymap, + WaylandCallbacks::keyboard_handle_enter, + WaylandCallbacks::keyboard_handle_leave, + WaylandCallbacks::keyboard_handle_key, + WaylandCallbacks::keyboard_handle_modifiers, + WaylandCallbacks::keyboard_handle_repeat_info +}; + +const struct wl_seat_listener WaylandCallbacks::seat_listener = { + WaylandCallbacks::seat_handle_capabilities, +}; + +const struct wl_output_listener WaylandCallbacks::output_listener = { + WaylandCallbacks::display_handle_geometry, + WaylandCallbacks::display_handle_mode, + WaylandCallbacks::display_handle_done, + WaylandCallbacks::display_handle_scale +}; + +const wl_registry_listener WaylandCallbacks::registry_listener = { + WaylandCallbacks::registry_add, + WaylandCallbacks::registry_remove, +}; + + +//const char* wmDeleteWindow = "WM_DELETE_WINDOW"; + +//! constructor +CIrrDeviceWayland::CIrrDeviceWayland(const SIrrlichtCreationParameters& param) + : CIrrDeviceStub(param), + Width(param.WindowSize.Width), Height(param.WindowSize.Height), + WindowHasFocus(false), WindowMinimized(false), + UseXVidMode(false), UseXRandR(false), UseGLXWindow(false), + ExternalWindow(false), AutorepeatSupport(0) +{ + #ifdef _DEBUG + setDebugName("CIrrDeviceLinux"); + #endif + + + // print version, distribution etc. + // thx to LynxLuna for pointing me to the uname function + core::stringc linuxversion; + struct utsname LinuxInfo; + uname(&LinuxInfo); + + linuxversion += LinuxInfo.sysname; + linuxversion += " "; + linuxversion += LinuxInfo.release; + linuxversion += " "; + linuxversion += LinuxInfo.version; + linuxversion += " "; + linuxversion += LinuxInfo.machine; + + Operator = new COSOperator(linuxversion); + os::Printer::log(linuxversion.c_str(), ELL_INFORMATION); + + // Retrieve wayland infos + display = wl_display_connect(NULL); + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, &WaylandCallbacks::registry_listener, this); + wl_display_dispatch(display); + + xkbctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + pointer = 0; + keyboard = 0; + + wl_seat_add_listener(seat, &WaylandCallbacks::seat_listener, this); + wl_output_add_listener(output, &WaylandCallbacks::output_listener, this); + wl_display_dispatch(display); + + // create keymap + createKeyMap(); + + // create window + if (CreationParams.DriverType != video::EDT_NULL) + { + // create the window, only if we do not use the null device + if (!createWindow()) + return; + } + + // create cursor control + CursorControl = new CCursorControl(this, CreationParams.DriverType == video::EDT_NULL); + + // create driver + createDriver(); + + if (!VideoDriver) + return; + + createGUIAndScene(); +} + + +//! destructor +CIrrDeviceWayland::~CIrrDeviceWayland() +{ + printf("destroy dev\n"); + wl_output_destroy(output); + wl_keyboard_destroy(keyboard); + wl_pointer_destroy(pointer); + wl_seat_destroy(seat); + wl_registry_destroy(registry); + wl_display_disconnect(display); + xkb_context_unref(xkbctx); +//TODO : surfaces, egl +#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + for (u32 joystick = 0; joystick < ActiveJoysticks.size(); ++joystick) + { + if (ActiveJoysticks[joystick].fd >= 0) + { + close(ActiveJoysticks[joystick].fd); + } + } +#endif +} + +bool CIrrDeviceWayland::restoreResolution() +{ + if (!CreationParams.Fullscreen) + return true; + return true; +} + + +bool CIrrDeviceWayland::changeResolution() +{ + if (!CreationParams.Fullscreen) + return true; + + getVideoModeList(); + + return CreationParams.Fullscreen; +} + + +void CIrrDeviceWayland::initEGL() +{ + egl_window = wl_egl_window_create(surface, Width, Height); + + egl_display = eglGetDisplay(display); + if (egl_display == EGL_NO_DISPLAY) + { + os::Printer::log("eglGetDisplay() error", "", ELL_ERROR); + } + if(!eglInitialize(egl_display, NULL, NULL)) + { + os::Printer::log("eglInitialize() error", "", ELL_ERROR); + } + + int confsize; + int attrib[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, 4, + EGL_GREEN_SIZE, 4, + EGL_BLUE_SIZE, 4, + EGL_DEPTH_SIZE, 24, + EGL_NONE}; + EGLConfig eglconf; + + if (!eglChooseConfig(egl_display, attrib, &eglconf, 1, &confsize)) + { + os::Printer::log("eglChooseConfig() error", "", ELL_ERROR); + } + egl_surface = eglCreateWindowSurface(egl_display, eglconf, egl_window, 0); + if (egl_surface == EGL_NO_SURFACE) + { + os::Printer::log("eglCreateWindowSurface() error", "", ELL_ERROR); + } + if (!eglBindAPI(EGL_OPENGL_API)) + { + os::Printer::log("eglBindAPI() error", "", ELL_ERROR); + } + int glattrib[] = {EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_NONE}; + egl_context = eglCreateContext(egl_display, eglconf, EGL_NO_CONTEXT, glattrib); + if (egl_context == EGL_NO_CONTEXT) + { + os::Printer::log("eglCreateContext() error", "", ELL_ERROR); + } + if (!eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context)) + { + os::Printer::log("eglMakeCurrent() error", "", ELL_ERROR); + } + video::useCoreContext = true; +} + +bool CIrrDeviceWayland::createWindow() +{ + surface = wl_compositor_create_surface(compositor); + shell_surface = wl_shell_get_shell_surface(shell, surface); + wl_shell_surface_set_toplevel(shell_surface); + + initEGL(); + + CreationParams.WindowSize.Width = Width; + CreationParams.WindowSize.Height = Height; + + return true; +} + + +//! create the driver +void CIrrDeviceWayland::createDriver() +{ + switch(CreationParams.DriverType) + { + default: + os::Printer::log("Wayland driver only supports OpenGL.", ELL_ERROR); + break; + case video::EDT_OPENGL: +// #ifdef _IRR_COMPILE_WITH_OPENGL_ +// if (Context) + VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, this); +/* #else + os::Printer::log("No OpenGL support compiled in.", ELL_ERROR); + #endif*/ + break; + } +} + + +//! runs the device. Returns false if device wants to be deleted +bool CIrrDeviceWayland::run() +{ + os::Timer::tick(); + +// printf("vents size is %d\n", events.size()); + for (unsigned i = 0; i < events.size(); i++) + { + postEventFromUser(events[i]); + +/* if (irrevent.MouseInput.Event != irr::EMIE_COUNT) + { + printf("posteventfromuser\n"); + bool v = postEventFromUser(irrevent); + printf("v is %d\n", v); + + if ( irrevent.MouseInput.Event >= EMIE_LMOUSE_PRESSED_DOWN && irrevent.MouseInput.Event <= EMIE_MMOUSE_PRESSED_DOWN ) + { + u32 clicks = checkSuccessiveClicks(irrevent.MouseInput.X, irrevent.MouseInput.Y, irrevent.MouseInput.Event); + if ( clicks == 2 ) + { + irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_DOUBLE_CLICK + irrevent.MouseInput.Event-EMIE_LMOUSE_PRESSED_DOWN); + postEventFromUser(irrevent); + } + else if ( clicks == 3 ) + { + irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_TRIPLE_CLICK + irrevent.MouseInput.Event-EMIE_LMOUSE_PRESSED_DOWN); + postEventFromUser(irrevent); + } + } + }*/ + } + events.clear(); + + + if (!Close) + pollJoysticks(); + + return !Close; +} + + +//! Pause the current process for the minimum time allowed only to allow other processes to execute +void CIrrDeviceWayland::yield() +{ + struct timespec ts = {0,1}; + nanosleep(&ts, NULL); +} + + +//! Pause execution and let other processes to run for a specified amount of time. +void CIrrDeviceWayland::sleep(u32 timeMs, bool pauseTimer=false) +{ + const bool wasStopped = Timer ? Timer->isStopped() : true; + + struct timespec ts; + ts.tv_sec = (time_t) (timeMs / 1000); + ts.tv_nsec = (long) (timeMs % 1000) * 1000000; + + if (pauseTimer && !wasStopped) + Timer->stop(); + + nanosleep(&ts, NULL); + + if (pauseTimer && !wasStopped) + Timer->start(); +} + +CIrrDeviceWayland::CCursorControl::CCursorControl(CIrrDeviceWayland* dev, bool null) + : Device(dev) + , IsVisible(true), Null(null), UseReferenceRect(false) + , ActiveIcon(gui::ECI_NORMAL), ActiveIconStartTime(0) +{ +} + +CIrrDeviceWayland::CCursorControl::~CCursorControl() +{ + // Do not clearCursors here as the display is already closed + // TODO (cutealien): droping cursorcontrol earlier might work, not sure about reason why that's done in stub currently. +} + +//! Sets the new position of the cursor. +void CIrrDeviceWayland::CCursorControl::setPosition(s32 x, s32 y) +{ + CursorPos = core::position2di(x / 256, y / 256); +} + +//! Returns the current position of the mouse cursor. +const core::position2d& CIrrDeviceWayland::CCursorControl::getPosition() +{ + return CursorPos; +} + +core::position2d CIrrDeviceWayland::CCursorControl::getRelativePosition() +{} + +//! Sets the active cursor icon +void CIrrDeviceWayland::CCursorControl::setActiveIcon(gui::ECURSOR_ICON iconId) +{ + +} + +void CIrrDeviceWayland::signalEvent(const SEvent &event) +{ + events.push_back(event); +} + +//! Add a custom sprite as cursor icon. +gui::ECURSOR_ICON CIrrDeviceWayland::CCursorControl::addIcon(const gui::SCursorSprite& icon) +{ + return gui::ECI_NORMAL; +} + +//! replace the given cursor icon. +void CIrrDeviceWayland::CCursorControl::changeIcon(gui::ECURSOR_ICON iconId, const gui::SCursorSprite& icon) +{ +} + +irr::core::dimension2di CIrrDeviceWayland::CCursorControl::getSupportedIconSize() const +{ + // this returns the closest match that is smaller or same size, so we just pass a value which should be large enough for cursors + unsigned int width=0, height=0; + return core::dimension2di(width, height); +} + +//! sets the caption of the window +void CIrrDeviceWayland::setWindowCaption(const wchar_t* text) +{ +} + + +//! presents a surface in the client area +bool CIrrDeviceWayland::present(video::IImage* image, void* windowId, core::rect* srcRect) +{ + return true; +} + + +//! notifies the device that it should close itself +void CIrrDeviceWayland::closeDevice() +{ + Close = true; +} + + +//! returns if window is active. if not, nothing need to be drawn +bool CIrrDeviceWayland::isWindowActive() const +{ + return (WindowHasFocus && !WindowMinimized); +} + + +//! returns if window has focus. +bool CIrrDeviceWayland::isWindowFocused() const +{ + return WindowHasFocus; +} + + +//! returns if window is minimized. +bool CIrrDeviceWayland::isWindowMinimized() const +{ + return WindowMinimized; +} + + +//! returns color format of the window. +video::ECOLOR_FORMAT CIrrDeviceWayland::getColorFormat() const +{ + return video::ECF_R8G8B8; +} + + +//! Sets if the window should be resizable in windowed mode. +void CIrrDeviceWayland::setResizable(bool resize) +{ +} + + +//! Return pointer to a list with all video modes supported by the gfx adapter. +video::IVideoModeList* CIrrDeviceWayland::getVideoModeList() +{ + printf("retrieve\n"); + return &VideoModeList; +} + + +//! Minimize window +void CIrrDeviceWayland::minimizeWindow() +{ + +} + + +//! Maximize window +void CIrrDeviceWayland::maximizeWindow() +{ + +} + + +//! Restore original window size +void CIrrDeviceWayland::restoreWindow() +{ + +} + + +void CIrrDeviceWayland::createKeyMap() +{ + KeyMap.reallocate(190); + KeyMap.push_back(SKeyMap(XKB_KEY_BackSpace, KEY_BACK)); + KeyMap.push_back(SKeyMap(XKB_KEY_Tab, KEY_TAB)); + KeyMap.push_back(SKeyMap(XKB_KEY_ISO_Left_Tab, KEY_TAB)); +// KeyMap.push_back(SKeyMap(XK_Linefeed, 0)); // ??? + KeyMap.push_back(SKeyMap(XKB_KEY_Clear, KEY_CLEAR)); + KeyMap.push_back(SKeyMap(XKB_KEY_Return, KEY_RETURN)); + KeyMap.push_back(SKeyMap(XKB_KEY_Pause, KEY_PAUSE)); + KeyMap.push_back(SKeyMap(XKB_KEY_Scroll_Lock, KEY_SCROLL)); +// KeyMap.push_back(SKeyMap(XK_Sys_Req, 0)); // ??? + KeyMap.push_back(SKeyMap(XKB_KEY_Escape, KEY_ESCAPE)); + KeyMap.push_back(SKeyMap(XKB_KEY_Insert, KEY_INSERT)); + KeyMap.push_back(SKeyMap(XKB_KEY_Delete, KEY_DELETE)); + KeyMap.push_back(SKeyMap(XKB_KEY_Home, KEY_HOME)); + KeyMap.push_back(SKeyMap(XKB_KEY_Left, KEY_LEFT)); + KeyMap.push_back(SKeyMap(XKB_KEY_Up, KEY_UP)); + KeyMap.push_back(SKeyMap(XKB_KEY_Right, KEY_RIGHT)); + KeyMap.push_back(SKeyMap(XKB_KEY_Down, KEY_DOWN)); + KeyMap.push_back(SKeyMap(XKB_KEY_Prior, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XKB_KEY_Page_Up, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XKB_KEY_Next, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XKB_KEY_Page_Down, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XKB_KEY_End, KEY_END)); + KeyMap.push_back(SKeyMap(XKB_KEY_Begin, KEY_HOME)); + KeyMap.push_back(SKeyMap(XKB_KEY_Num_Lock, KEY_NUMLOCK)); + KeyMap.push_back(SKeyMap(XKB_KEY_space, KEY_SPACE)); + /* TODO : keypad */ +/* KeyMap.push_back(SKeyMap(XK_KP_Tab, KEY_TAB)); + KeyMap.push_back(SKeyMap(XK_KP_Enter, KEY_RETURN)); + KeyMap.push_back(SKeyMap(XK_KP_F1, KEY_F1)); + KeyMap.push_back(SKeyMap(XK_KP_F2, KEY_F2)); + KeyMap.push_back(SKeyMap(XK_KP_F3, KEY_F3)); + KeyMap.push_back(SKeyMap(XK_KP_F4, KEY_F4)); + KeyMap.push_back(SKeyMap(XK_KP_Home, KEY_HOME)); + KeyMap.push_back(SKeyMap(XK_KP_Left, KEY_LEFT)); + KeyMap.push_back(SKeyMap(XK_KP_Up, KEY_UP)); + KeyMap.push_back(SKeyMap(XK_KP_Right, KEY_RIGHT)); + KeyMap.push_back(SKeyMap(XK_KP_Down, KEY_DOWN));*/ + KeyMap.push_back(SKeyMap(XKB_KEY_Print, KEY_PRINT)); +/* KeyMap.push_back(SKeyMap(XK_KP_Prior, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XK_KP_Page_Up, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XK_KP_Next, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XK_KP_Page_Down, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XK_KP_End, KEY_END)); + KeyMap.push_back(SKeyMap(XK_KP_Begin, KEY_HOME)); + KeyMap.push_back(SKeyMap(XK_KP_Insert, KEY_INSERT)); + KeyMap.push_back(SKeyMap(XK_KP_Delete, KEY_DELETE)); + KeyMap.push_back(SKeyMap(XK_KP_Equal, 0)); // ??? + KeyMap.push_back(SKeyMap(XK_KP_Multiply, KEY_MULTIPLY)); + KeyMap.push_back(SKeyMap(XK_KP_Add, KEY_ADD)); + KeyMap.push_back(SKeyMap(XK_KP_Separator, KEY_SEPARATOR)); + KeyMap.push_back(SKeyMap(XK_KP_Subtract, KEY_SUBTRACT)); + KeyMap.push_back(SKeyMap(XK_KP_Decimal, KEY_DECIMAL)); + KeyMap.push_back(SKeyMap(XK_KP_Divide, KEY_DIVIDE)); + KeyMap.push_back(SKeyMap(XK_KP_0, KEY_NUMPAD0)); + KeyMap.push_back(SKeyMap(XK_KP_1, KEY_NUMPAD1)); + KeyMap.push_back(SKeyMap(XK_KP_2, KEY_NUMPAD2)); + KeyMap.push_back(SKeyMap(XK_KP_3, KEY_NUMPAD3)); + KeyMap.push_back(SKeyMap(XK_KP_4, KEY_NUMPAD4)); + KeyMap.push_back(SKeyMap(XK_KP_5, KEY_NUMPAD5)); + KeyMap.push_back(SKeyMap(XK_KP_6, KEY_NUMPAD6)); + KeyMap.push_back(SKeyMap(XK_KP_7, KEY_NUMPAD7)); + KeyMap.push_back(SKeyMap(XK_KP_8, KEY_NUMPAD8)); + KeyMap.push_back(SKeyMap(XK_KP_9, KEY_NUMPAD9));*/ + KeyMap.push_back(SKeyMap(XKB_KEY_F1, KEY_F1)); + KeyMap.push_back(SKeyMap(XKB_KEY_F2, KEY_F2)); + KeyMap.push_back(SKeyMap(XKB_KEY_F3, KEY_F3)); + KeyMap.push_back(SKeyMap(XKB_KEY_F4, KEY_F4)); + KeyMap.push_back(SKeyMap(XKB_KEY_F5, KEY_F5)); + KeyMap.push_back(SKeyMap(XKB_KEY_F6, KEY_F6)); + KeyMap.push_back(SKeyMap(XKB_KEY_F7, KEY_F7)); + KeyMap.push_back(SKeyMap(XKB_KEY_F8, KEY_F8)); + KeyMap.push_back(SKeyMap(XKB_KEY_F9, KEY_F9)); + KeyMap.push_back(SKeyMap(XKB_KEY_F10, KEY_F10)); + KeyMap.push_back(SKeyMap(XKB_KEY_F11, KEY_F11)); + KeyMap.push_back(SKeyMap(XKB_KEY_F12, KEY_F12)); + KeyMap.push_back(SKeyMap(XKB_KEY_Shift_L, KEY_LSHIFT)); + KeyMap.push_back(SKeyMap(XKB_KEY_Shift_R, KEY_RSHIFT)); + KeyMap.push_back(SKeyMap(XKB_KEY_Control_L, KEY_LCONTROL)); + KeyMap.push_back(SKeyMap(XKB_KEY_Control_R, KEY_RCONTROL)); + KeyMap.push_back(SKeyMap(XKB_KEY_Caps_Lock, KEY_CAPITAL)); + KeyMap.push_back(SKeyMap(XKB_KEY_Shift_Lock, KEY_CAPITAL)); + KeyMap.push_back(SKeyMap(XKB_KEY_Meta_L, KEY_LWIN)); + KeyMap.push_back(SKeyMap(XKB_KEY_Meta_R, KEY_RWIN)); + KeyMap.push_back(SKeyMap(XKB_KEY_Alt_L, KEY_LMENU)); + KeyMap.push_back(SKeyMap(XKB_KEY_Alt_R, KEY_RMENU)); + KeyMap.push_back(SKeyMap(XKB_KEY_ISO_Level3_Shift, KEY_RMENU)); + KeyMap.push_back(SKeyMap(XKB_KEY_Menu, KEY_MENU)); + KeyMap.push_back(SKeyMap(XKB_KEY_space, KEY_SPACE)); +// KeyMap.push_back(SKeyMap(XKB_key_ex, 0)); //? +// KeyMap.push_back(SKeyMap(XK_quotedbl, 0)); //? +// KeyMap.push_back(SKeyMap(XK_section, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_numbersign, KEY_OEM_2)); +// KeyMap.push_back(SKeyMap(XK_dollar, 0)); //? +// KeyMap.push_back(SKeyMap(XK_percent, 0)); //? +// KeyMap.push_back(SKeyMap(XK_ampersand, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_apostrophe, KEY_OEM_7)); +// KeyMap.push_back(SKeyMap(XK_parenleft, 0)); //? +// KeyMap.push_back(SKeyMap(XK_parenright, 0)); //? +// KeyMap.push_back(SKeyMap(XK_asterisk, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_plus, KEY_PLUS)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_comma, KEY_COMMA)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_minus, KEY_MINUS)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_period, KEY_PERIOD)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_slash, KEY_OEM_2)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_0, KEY_KEY_0)); + KeyMap.push_back(SKeyMap(XKB_KEY_1, KEY_KEY_1)); + KeyMap.push_back(SKeyMap(XKB_KEY_2, KEY_KEY_2)); + KeyMap.push_back(SKeyMap(XKB_KEY_3, KEY_KEY_3)); + KeyMap.push_back(SKeyMap(XKB_KEY_4, KEY_KEY_4)); + KeyMap.push_back(SKeyMap(XKB_KEY_5, KEY_KEY_5)); + KeyMap.push_back(SKeyMap(XKB_KEY_6, KEY_KEY_6)); + KeyMap.push_back(SKeyMap(XKB_KEY_7, KEY_KEY_7)); + KeyMap.push_back(SKeyMap(XKB_KEY_8, KEY_KEY_8)); + KeyMap.push_back(SKeyMap(XKB_KEY_9, KEY_KEY_9)); +// KeyMap.push_back(SKeyMap(XK_colon, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_semicolon, KEY_OEM_1)); + KeyMap.push_back(SKeyMap(XKB_KEY_less, KEY_OEM_102)); + KeyMap.push_back(SKeyMap(XKB_KEY_equal, KEY_PLUS)); +// KeyMap.push_back(SKeyMap(XK_greater, 0)); //? +// KeyMap.push_back(SKeyMap(XK_question, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_at, KEY_KEY_2)); //? +// KeyMap.push_back(SKeyMap(XK_mu, 0)); //? +// KeyMap.push_back(SKeyMap(XK_EuroSign, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_A, KEY_KEY_A)); + KeyMap.push_back(SKeyMap(XKB_KEY_B, KEY_KEY_B)); + KeyMap.push_back(SKeyMap(XKB_KEY_C, KEY_KEY_C)); + KeyMap.push_back(SKeyMap(XKB_KEY_D, KEY_KEY_D)); + KeyMap.push_back(SKeyMap(XKB_KEY_E, KEY_KEY_E)); + KeyMap.push_back(SKeyMap(XKB_KEY_F, KEY_KEY_F)); + KeyMap.push_back(SKeyMap(XKB_KEY_G, KEY_KEY_G)); + KeyMap.push_back(SKeyMap(XKB_KEY_H, KEY_KEY_H)); + KeyMap.push_back(SKeyMap(XKB_KEY_I, KEY_KEY_I)); + KeyMap.push_back(SKeyMap(XKB_KEY_J, KEY_KEY_J)); + KeyMap.push_back(SKeyMap(XKB_KEY_K, KEY_KEY_K)); + KeyMap.push_back(SKeyMap(XKB_KEY_L, KEY_KEY_L)); + KeyMap.push_back(SKeyMap(XKB_KEY_M, KEY_KEY_M)); + KeyMap.push_back(SKeyMap(XKB_KEY_N, KEY_KEY_N)); + KeyMap.push_back(SKeyMap(XKB_KEY_O, KEY_KEY_O)); + KeyMap.push_back(SKeyMap(XKB_KEY_P, KEY_KEY_P)); + KeyMap.push_back(SKeyMap(XKB_KEY_Q, KEY_KEY_Q)); + KeyMap.push_back(SKeyMap(XKB_KEY_R, KEY_KEY_R)); + KeyMap.push_back(SKeyMap(XKB_KEY_S, KEY_KEY_S)); + KeyMap.push_back(SKeyMap(XKB_KEY_T, KEY_KEY_T)); + KeyMap.push_back(SKeyMap(XKB_KEY_U, KEY_KEY_U)); + KeyMap.push_back(SKeyMap(XKB_KEY_V, KEY_KEY_V)); + KeyMap.push_back(SKeyMap(XKB_KEY_W, KEY_KEY_W)); + KeyMap.push_back(SKeyMap(XKB_KEY_X, KEY_KEY_X)); + KeyMap.push_back(SKeyMap(XKB_KEY_Y, KEY_KEY_Y)); + KeyMap.push_back(SKeyMap(XKB_KEY_Z, KEY_KEY_Z)); + KeyMap.push_back(SKeyMap(XKB_KEY_bracketleft, KEY_OEM_4)); + KeyMap.push_back(SKeyMap(XKB_KEY_backslash, KEY_OEM_5)); + KeyMap.push_back(SKeyMap(XKB_KEY_bracketright, KEY_OEM_6)); + KeyMap.push_back(SKeyMap(XKB_KEY_asciicircum, KEY_OEM_5)); +// KeyMap.push_back(SKeyMap(XK_degree, 0)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_underscore, KEY_MINUS)); //? + KeyMap.push_back(SKeyMap(XKB_KEY_grave, KEY_OEM_3)); + KeyMap.push_back(SKeyMap(XKB_KEY_acute, KEY_OEM_6)); + KeyMap.push_back(SKeyMap(XKB_KEY_a, KEY_KEY_A)); + KeyMap.push_back(SKeyMap(XKB_KEY_b, KEY_KEY_B)); + KeyMap.push_back(SKeyMap(XKB_KEY_c, KEY_KEY_C)); + KeyMap.push_back(SKeyMap(XKB_KEY_c, KEY_KEY_D)); + KeyMap.push_back(SKeyMap(XKB_KEY_e, KEY_KEY_E)); + KeyMap.push_back(SKeyMap(XKB_KEY_f, KEY_KEY_F)); + KeyMap.push_back(SKeyMap(XKB_KEY_g, KEY_KEY_G)); + KeyMap.push_back(SKeyMap(XKB_KEY_h, KEY_KEY_H)); + KeyMap.push_back(SKeyMap(XKB_KEY_i, KEY_KEY_I)); + KeyMap.push_back(SKeyMap(XKB_KEY_j, KEY_KEY_J)); + KeyMap.push_back(SKeyMap(XKB_KEY_k, KEY_KEY_K)); + KeyMap.push_back(SKeyMap(XKB_KEY_l, KEY_KEY_L)); + KeyMap.push_back(SKeyMap(XKB_KEY_m, KEY_KEY_M)); + KeyMap.push_back(SKeyMap(XKB_KEY_n, KEY_KEY_N)); + KeyMap.push_back(SKeyMap(XKB_KEY_o, KEY_KEY_O)); + KeyMap.push_back(SKeyMap(XKB_KEY_p, KEY_KEY_P)); + KeyMap.push_back(SKeyMap(XKB_KEY_q, KEY_KEY_Q)); + KeyMap.push_back(SKeyMap(XKB_KEY_r, KEY_KEY_R)); + KeyMap.push_back(SKeyMap(XKB_KEY_s, KEY_KEY_S)); + KeyMap.push_back(SKeyMap(XKB_KEY_t, KEY_KEY_T)); + KeyMap.push_back(SKeyMap(XKB_KEY_u, KEY_KEY_U)); + KeyMap.push_back(SKeyMap(XKB_KEY_v, KEY_KEY_V)); + KeyMap.push_back(SKeyMap(XKB_KEY_w, KEY_KEY_W)); + KeyMap.push_back(SKeyMap(XKB_KEY_x, KEY_KEY_X)); + KeyMap.push_back(SKeyMap(XKB_KEY_y, KEY_KEY_Y)); + KeyMap.push_back(SKeyMap(XKB_KEY_z, KEY_KEY_Z)); + KeyMap.push_back(SKeyMap(XKB_KEY_ssharp, KEY_OEM_4)); + KeyMap.push_back(SKeyMap(XKB_KEY_adiaeresis, KEY_OEM_7)); + KeyMap.push_back(SKeyMap(XKB_KEY_odiaeresis, KEY_OEM_3)); + KeyMap.push_back(SKeyMap(XKB_KEY_udiaeresis, KEY_OEM_1)); + KeyMap.push_back(SKeyMap(XKB_KEY_Super_L, KEY_LWIN)); + KeyMap.push_back(SKeyMap(XKB_KEY_Super_R, KEY_RWIN)); + + KeyMap.sort(); + + +} + +bool CIrrDeviceWayland::activateJoysticks(core::array & joystickInfo) +{ +#if defined (_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + + joystickInfo.clear(); + + u32 joystick; + for (joystick = 0; joystick < 32; ++joystick) + { + // The joystick device could be here... + core::stringc devName = "/dev/js"; + devName += joystick; + + SJoystickInfo returnInfo; + JoystickInfo info; + + info.fd = open(devName.c_str(), O_RDONLY); + if (-1 == info.fd) + { + // ...but Ubuntu and possibly other distros + // create the devices in /dev/input + devName = "/dev/input/js"; + devName += joystick; + info.fd = open(devName.c_str(), O_RDONLY); + if (-1 == info.fd) + { + // and BSD here + devName = "/dev/joy"; + devName += joystick; + info.fd = open(devName.c_str(), O_RDONLY); + } + } + + if (-1 == info.fd) + continue; + +#ifdef __FreeBSD__ + info.axes=2; + info.buttons=2; +#else + ioctl( info.fd, JSIOCGAXES, &(info.axes) ); + ioctl( info.fd, JSIOCGBUTTONS, &(info.buttons) ); + fcntl( info.fd, F_SETFL, O_NONBLOCK ); +#endif + + (void)memset(&info.persistentData, 0, sizeof(info.persistentData)); + info.persistentData.EventType = irr::EET_JOYSTICK_INPUT_EVENT; + info.persistentData.JoystickEvent.Joystick = ActiveJoysticks.size(); + + // There's no obvious way to determine which (if any) axes represent a POV + // hat, so we'll just set it to "not used" and forget about it. + info.persistentData.JoystickEvent.POV = 65535; + + ActiveJoysticks.push_back(info); + + returnInfo.HasGenericName = false; + returnInfo.Joystick = joystick; + returnInfo.PovHat = SJoystickInfo::POV_HAT_UNKNOWN; + returnInfo.Axes = info.axes; + returnInfo.Buttons = info.buttons; + +#ifndef __FreeBSD__ + char name[80]; + ioctl( info.fd, JSIOCGNAME(80), name); + returnInfo.Name = name; +#endif + + joystickInfo.push_back(returnInfo); + } + + for (joystick = 0; joystick < joystickInfo.size(); ++joystick) + { + char logString[256]; + (void)sprintf(logString, "Found joystick %u, %u axes, %u buttons '%s'", + joystick, joystickInfo[joystick].Axes, + joystickInfo[joystick].Buttons, joystickInfo[joystick].Name.c_str()); + os::Printer::log(logString, ELL_INFORMATION); + } + + return true; +#else + return false; +#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ +} + + +void CIrrDeviceWayland::pollJoysticks() +{ +#if defined (_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + if (0 == ActiveJoysticks.size()) + return; + + for (u32 j= 0; j< ActiveJoysticks.size(); ++j) + { + JoystickInfo & info = ActiveJoysticks[j]; + +#ifdef __FreeBSD__ + struct joystick js; + if (read(info.fd, &js, sizeof(js)) == sizeof(js)) + { + info.persistentData.JoystickEvent.ButtonStates = js.b1 | (js.b2 << 1); /* should be a two-bit field */ + info.persistentData.JoystickEvent.Axis[0] = js.x; /* X axis */ + info.persistentData.JoystickEvent.Axis[1] = js.y; /* Y axis */ + } +#else + struct js_event event; + while (sizeof(event) == read(info.fd, &event, sizeof(event))) + { + switch(event.type & ~JS_EVENT_INIT) + { + case JS_EVENT_BUTTON: + if (event.value) + info.persistentData.JoystickEvent.ButtonStates |= (1 << event.number); + else + info.persistentData.JoystickEvent.ButtonStates &= ~(1 << event.number); + break; + + case JS_EVENT_AXIS: + if (event.number < SEvent::SJoystickEvent::NUMBER_OF_AXES) + info.persistentData.JoystickEvent.Axis[event.number] = event.value; + break; + + default: + break; + } + } +#endif + + // Send an irrlicht joystick event once per ::run() even if no new data were received. + (void)postEventFromUser(info.persistentData); + } +#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ +} + + +//! Set the current Gamma Value for the Display +bool CIrrDeviceWayland::setGammaRamp( f32 red, f32 green, f32 blue, f32 brightness, f32 contrast ) +{ + return false; +} + + +//! Get the current Gamma Value for the Display +bool CIrrDeviceWayland::getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ) +{ + brightness = 0.f; + contrast = 0.f; + return false; +} + + +//! gets text from the clipboard +//! \return Returns 0 if no string is in there. +const c8* CIrrDeviceWayland::getTextFromClipboard() const +{ + return 0; + +} + +//! copies text to the clipboard +void CIrrDeviceWayland::copyToClipboard(const c8* text) const +{ +} + + +//! Remove all messages pending in the system message loop +void CIrrDeviceWayland::clearSystemMessages() +{ +} + +void CIrrDeviceWayland::initXAtoms() +{ +} + +} // end namespace diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.h b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.h new file mode 100644 index 000000000..5f5976414 --- /dev/null +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.h @@ -0,0 +1,309 @@ +#ifndef CIRRDEVICEWAYLAND_H +#define CIRRDEVICEWAYLAND_H + +#include "IrrCompileConfig.h" + +#include "CIrrDeviceStub.h" +#include "IrrlichtDevice.h" +#include "IImagePresenter.h" +#include "ICursorControl.h" +#include "os.h" + +#include +#include +#include + +#include +#include +#include + +#include +#define KeySym s32 + +// Note : only supporting shell interface + +namespace irr +{ + + class CIrrDeviceWayland : public CIrrDeviceStub, public video::IImagePresenter + { + public: + + //! constructor + CIrrDeviceWayland(const SIrrlichtCreationParameters& param); + + //! destructor + virtual ~CIrrDeviceWayland(); + + //! runs the device. Returns false if device wants to be deleted + virtual bool run(); + + //! Cause the device to temporarily pause execution and let other processes to run + // This should bring down processor usage without major performance loss for Irrlicht + virtual void yield(); + + //! Pause execution and let other processes to run for a specified amount of time. + virtual void sleep(u32 timeMs, bool pauseTimer); + + //! sets the caption of the window + virtual void setWindowCaption(const wchar_t* text); + + //! returns if window is active. if not, nothing need to be drawn + virtual bool isWindowActive() const; + + //! returns if window has focus. + virtual bool isWindowFocused() const; + + //! returns if window is minimized. + virtual bool isWindowMinimized() const; + + //! returns color format of the window. + virtual video::ECOLOR_FORMAT getColorFormat() const; + + //! presents a surface in the client area + virtual bool present(video::IImage* surface, void* windowId=0, core::rect* src=0 ); + + //! notifies the device that it should close itself + virtual void closeDevice(); + + //! \return Returns a pointer to a list with all video modes + //! supported by the gfx adapter. + video::IVideoModeList* getVideoModeList(); + + //! Sets if the window should be resizable in windowed mode. + virtual void setResizable(bool resize=false); + + //! Minimizes the window. + virtual void minimizeWindow(); + + //! Maximizes the window. + virtual void maximizeWindow(); + + //! Restores the window size. + virtual void restoreWindow(); + + //! Activate any joysticks, and generate events for them. + virtual bool activateJoysticks(core::array & joystickInfo); + + //! Set the current Gamma Value for the Display + virtual bool setGammaRamp( f32 red, f32 green, f32 blue, f32 brightness, f32 contrast ); + + //! Get the current Gamma Value for the Display + virtual bool getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ); + + //! gets text from the clipboard + //! \return Returns 0 if no string is in there. + virtual const c8* getTextFromClipboard() const; + + //! copies text to the clipboard + //! This sets the clipboard selection and _not_ the primary selection which you have on X on the middle mouse button. + virtual void copyToClipboard(const c8* text) const; + + //! Remove all messages pending in the system message loop + virtual void clearSystemMessages(); + + //! Get the device type + virtual E_DEVICE_TYPE getType() const + { + return EIDT_WAYLAND; + } + + private: + + //! create the driver + void createDriver(); + + void initEGL(); + + bool createWindow(); + + void createKeyMap(); + + void pollJoysticks(); + + void initXAtoms(); + + bool restoreResolution(); + bool changeResolution(); + + //! Implementation of the linux cursor control + class CCursorControl : public gui::ICursorControl + { + public: + + CCursorControl(CIrrDeviceWayland* dev, bool null); + + ~CCursorControl(); + + //! Changes the visible state of the mouse cursor. + virtual void setVisible(bool visible) + { + if (visible==IsVisible) + return; + IsVisible = visible; + } + + //! Returns if the cursor is currently visible. + virtual bool isVisible() const + { + return IsVisible; + } + + //! Sets the new position of the cursor. + virtual void setPosition(const core::position2d &pos) + { + setPosition(pos.X, pos.Y); + } + + //! Sets the new position of the cursor. + virtual void setPosition(f32 x, f32 y) + { + setPosition((s32)(x*Device->Width), (s32)(y*Device->Height)); + } + + //! Sets the new position of the cursor. + virtual void setPosition(const core::position2d &pos) + { + setPosition(pos.X, pos.Y); + } + + //! Sets the new position of the cursor. + virtual void setPosition(s32 x, s32 y); + + //! Returns the current position of the mouse cursor. + virtual const core::position2d& getPosition(); + virtual core::position2d getRelativePosition(); + + virtual void setReferenceRect(core::rect* rect=0) + { + if (rect) + { + ReferenceRect = *rect; + UseReferenceRect = true; + + // prevent division through zero and uneven sizes + + if (!ReferenceRect.getHeight() || ReferenceRect.getHeight()%2) + ReferenceRect.LowerRightCorner.Y += 1; + + if (!ReferenceRect.getWidth() || ReferenceRect.getWidth()%2) + ReferenceRect.LowerRightCorner.X += 1; + } + else + UseReferenceRect = false; + } + + //! Sets the active cursor icon + virtual void setActiveIcon(gui::ECURSOR_ICON iconId); + + //! Gets the currently active icon + virtual gui::ECURSOR_ICON getActiveIcon() const + { + return ActiveIcon; + } + + //! Add a custom sprite as cursor icon. + virtual gui::ECURSOR_ICON addIcon(const gui::SCursorSprite& icon); + + //! replace the given cursor icon. + virtual void changeIcon(gui::ECURSOR_ICON iconId, const gui::SCursorSprite& icon); + + //! Return a system-specific size which is supported for cursors. Larger icons will fail, smaller icons might work. + virtual core::dimension2di getSupportedIconSize() const; + private: + + CIrrDeviceWayland* Device; + core::position2d CursorPos; + core::rect ReferenceRect; + bool IsVisible; + bool Null; + bool UseReferenceRect; + gui::ECURSOR_ICON ActiveIcon; + u32 ActiveIconStartTime; + }; + + friend class CCursorControl; + + friend class COpenGLDriver; + + friend class WaylandCallbacks; + std::vector events; + std::vector Modes; + core::dimension2du CurrentModes; + + public: + void signalEvent(const SEvent&); + void addMode(const core::dimension2du &mode) { Modes.push_back(mode); } + void setCurrentMode(const core::dimension2du &mode) { CurrentModes = mode; } + + wl_display *display; + wl_registry *registry; + wl_compositor *compositor; + wl_seat *seat; + wl_pointer *pointer; + wl_keyboard *keyboard; + wl_output *output; + xkb_context *xkbctx; + xkb_keymap *keymap; + xkb_state *state; + + wl_shell *shell; + wl_surface *surface; + wl_shell_surface *shell_surface; + wl_egl_window *egl_window; + + EGLSurface egl_surface; + EGLDisplay egl_display; + EGLContext egl_context; + private: +// XVisualInfo* visual; + mutable core::stringc Clipboard; + + u32 Width, Height; + bool WindowHasFocus; + bool WindowMinimized; + bool UseXVidMode; + bool UseXRandR; + bool UseGLXWindow; + bool ExternalWindow; + int AutorepeatSupport; +public: + struct SKeyMap + { + SKeyMap() {} + SKeyMap(s32 x11, s32 win32) + : X11Key(x11), Win32Key(win32) + { + } + + KeySym X11Key; + s32 Win32Key; + + bool operator<(const SKeyMap& o) const + { + return X11Key KeyMap; +private: +#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) + struct JoystickInfo + { + int fd; + int axes; + int buttons; + + SEvent persistentData; + + JoystickInfo() : fd(-1), axes(0), buttons(0) { } + }; + core::array ActiveJoysticks; +#endif + }; + + +} // end namespace irr + +#endif // CIRRDEVICEWAYLAND_H + diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index a209d551c..532594cdd 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -367,7 +367,6 @@ void IrrDriver::initDevice() if (modes->getVideoModeCount() > 0) { res = modes->getVideoModeResolution(res, res); - UserConfigParams::m_width = res.Width; UserConfigParams::m_height = res.Height; } @@ -529,7 +528,7 @@ void IrrDriver::initDevice() // Only change video driver settings if we are showing graphics if (!ProfileWorld::isNoGraphics()) { -#if defined(__linux__) && !defined(ANDROID) +#if 0//defined(__linux__) && !defined(ANDROID) // Set class hints on Linux, used by Window Managers. const video::SExposedVideoData& videoData = m_video_driver ->getExposedVideoData(); @@ -584,7 +583,7 @@ void IrrDriver::initDevice() // set cursor visible by default (what's the default is not too clearly documented, // so let's decide ourselves...) - m_device->getCursorControl()->setVisible(true); + //m_device->getCursorControl()->setVisible(true); m_pointer_shown = true; } // initDevice @@ -632,7 +631,7 @@ void IrrDriver::showPointer() if (!m_pointer_shown) { m_pointer_shown = true; - this->getDevice()->getCursorControl()->setVisible(true); +// this->getDevice()->getCursorControl()->setVisible(true); } } // showPointer @@ -642,14 +641,14 @@ void IrrDriver::hidePointer() // always visible in artist debug mode, to be able to use the context menu if (UserConfigParams::m_artist_debug_mode) { - this->getDevice()->getCursorControl()->setVisible(true); +// this->getDevice()->getCursorControl()->setVisible(true); return; } if (m_pointer_shown) { m_pointer_shown = false; - this->getDevice()->getCursorControl()->setVisible(false); +// this->getDevice()->getCursorControl()->setVisible(false); } } // hidePointer From 73a82202038a4acc81462b904304ec4ff761b7aa Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Sun, 1 Feb 2015 19:13:37 +0100 Subject: [PATCH 002/378] Improve mouse support --- .../source/Irrlicht/cirrdevicewayland.cpp | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp index c7f78bffa..a58db4c22 100644 --- a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp @@ -77,6 +77,20 @@ public: { CIrrDeviceWayland *dev = static_cast(data); dev->getCursorControl()->setPosition(sx, sy); + SEvent irrevent; + irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; + irrevent.MouseInput.X = dev->getCursorControl()->getPosition().X; + irrevent.MouseInput.Y = dev->getCursorControl()->getPosition().Y; +// irrevent.MouseInput.Control = (event.xkey.state & ControlMask) != 0; +// irrevent.MouseInput.Shift = (event.xkey.state & ShiftMask) != 0; + + // mouse button states + irrevent.MouseInput.ButtonStates = 0;//(event.xbutton.state & Button1Mask) ? irr::EMBSM_LEFT : 0; + irrevent.MouseInput.ButtonStates |= 0;//(event.xbutton.state & Button3Mask) ? irr::EMBSM_RIGHT : 0; + irrevent.MouseInput.ButtonStates |= 0;//(event.xbutton.state & Button2Mask) ? irr::EMBSM_MIDDLE : 0; + + dev->signalEvent(irrevent); } static void @@ -104,7 +118,6 @@ public: // irrevent.MouseInput.ButtonStates |= (event.xbutton.state & Button2Mask) ? irr::EMBSM_MIDDLE : 0; irrevent.MouseInput.Event = irr::EMIE_COUNT; - switch(button) { case 272: // Left button @@ -112,6 +125,15 @@ public: (state == WL_POINTER_BUTTON_STATE_PRESSED) ? irr::EMIE_LMOUSE_PRESSED_DOWN : irr::EMIE_LMOUSE_LEFT_UP; irrevent.MouseInput.ButtonStates ^= irr::EMBSM_LEFT; break; + case 273: // Right button + irrevent.MouseInput.Event = + (state == WL_POINTER_BUTTON_STATE_PRESSED) ? irr::EMIE_RMOUSE_PRESSED_DOWN : irr::EMIE_RMOUSE_LEFT_UP; + irrevent.MouseInput.ButtonStates ^= irr::EMBSM_RIGHT; + break; + case 274: // Middle button + irrevent.MouseInput.Event = + (state == WL_POINTER_BUTTON_STATE_PRESSED) ? irr::EMIE_MMOUSE_PRESSED_DOWN : irr::EMIE_MMOUSE_LEFT_UP; + irrevent.MouseInput.ButtonStates ^= irr::EMBSM_MIDDLE; default: break; } From da449e02d0c49a4ceb9b71bc9e356f4160dde0b7 Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Sun, 1 Feb 2015 19:18:47 +0100 Subject: [PATCH 003/378] Improve keypad support --- .../source/Irrlicht/cirrdevicewayland.cpp | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp index a58db4c22..c22d81a0e 100644 --- a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp @@ -881,44 +881,43 @@ void CIrrDeviceWayland::createKeyMap() KeyMap.push_back(SKeyMap(XKB_KEY_Begin, KEY_HOME)); KeyMap.push_back(SKeyMap(XKB_KEY_Num_Lock, KEY_NUMLOCK)); KeyMap.push_back(SKeyMap(XKB_KEY_space, KEY_SPACE)); - /* TODO : keypad */ -/* KeyMap.push_back(SKeyMap(XK_KP_Tab, KEY_TAB)); - KeyMap.push_back(SKeyMap(XK_KP_Enter, KEY_RETURN)); - KeyMap.push_back(SKeyMap(XK_KP_F1, KEY_F1)); - KeyMap.push_back(SKeyMap(XK_KP_F2, KEY_F2)); - KeyMap.push_back(SKeyMap(XK_KP_F3, KEY_F3)); - KeyMap.push_back(SKeyMap(XK_KP_F4, KEY_F4)); - KeyMap.push_back(SKeyMap(XK_KP_Home, KEY_HOME)); - KeyMap.push_back(SKeyMap(XK_KP_Left, KEY_LEFT)); - KeyMap.push_back(SKeyMap(XK_KP_Up, KEY_UP)); - KeyMap.push_back(SKeyMap(XK_KP_Right, KEY_RIGHT)); - KeyMap.push_back(SKeyMap(XK_KP_Down, KEY_DOWN));*/ + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Tab, KEY_TAB)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Enter, KEY_RETURN)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_F1, KEY_F1)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_F2, KEY_F2)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_F3, KEY_F3)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_F4, KEY_F4)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Home, KEY_HOME)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Left, KEY_LEFT)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Up, KEY_UP)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Right, KEY_RIGHT)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Down, KEY_DOWN)); KeyMap.push_back(SKeyMap(XKB_KEY_Print, KEY_PRINT)); -/* KeyMap.push_back(SKeyMap(XK_KP_Prior, KEY_PRIOR)); - KeyMap.push_back(SKeyMap(XK_KP_Page_Up, KEY_PRIOR)); - KeyMap.push_back(SKeyMap(XK_KP_Next, KEY_NEXT)); - KeyMap.push_back(SKeyMap(XK_KP_Page_Down, KEY_NEXT)); - KeyMap.push_back(SKeyMap(XK_KP_End, KEY_END)); - KeyMap.push_back(SKeyMap(XK_KP_Begin, KEY_HOME)); - KeyMap.push_back(SKeyMap(XK_KP_Insert, KEY_INSERT)); - KeyMap.push_back(SKeyMap(XK_KP_Delete, KEY_DELETE)); - KeyMap.push_back(SKeyMap(XK_KP_Equal, 0)); // ??? - KeyMap.push_back(SKeyMap(XK_KP_Multiply, KEY_MULTIPLY)); - KeyMap.push_back(SKeyMap(XK_KP_Add, KEY_ADD)); - KeyMap.push_back(SKeyMap(XK_KP_Separator, KEY_SEPARATOR)); - KeyMap.push_back(SKeyMap(XK_KP_Subtract, KEY_SUBTRACT)); - KeyMap.push_back(SKeyMap(XK_KP_Decimal, KEY_DECIMAL)); - KeyMap.push_back(SKeyMap(XK_KP_Divide, KEY_DIVIDE)); - KeyMap.push_back(SKeyMap(XK_KP_0, KEY_NUMPAD0)); - KeyMap.push_back(SKeyMap(XK_KP_1, KEY_NUMPAD1)); - KeyMap.push_back(SKeyMap(XK_KP_2, KEY_NUMPAD2)); - KeyMap.push_back(SKeyMap(XK_KP_3, KEY_NUMPAD3)); - KeyMap.push_back(SKeyMap(XK_KP_4, KEY_NUMPAD4)); - KeyMap.push_back(SKeyMap(XK_KP_5, KEY_NUMPAD5)); - KeyMap.push_back(SKeyMap(XK_KP_6, KEY_NUMPAD6)); - KeyMap.push_back(SKeyMap(XK_KP_7, KEY_NUMPAD7)); - KeyMap.push_back(SKeyMap(XK_KP_8, KEY_NUMPAD8)); - KeyMap.push_back(SKeyMap(XK_KP_9, KEY_NUMPAD9));*/ + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Prior, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Page_Up, KEY_PRIOR)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Next, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Page_Down, KEY_NEXT)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_End, KEY_END)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Begin, KEY_HOME)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Insert, KEY_INSERT)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Delete, KEY_DELETE)); +// KeyMap.push_back(SKeyMap(XK_KP_Equal, 0)); // ??? + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Multiply, KEY_MULTIPLY)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Add, KEY_ADD)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Separator, KEY_SEPARATOR)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Subtract, KEY_SUBTRACT)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Decimal, KEY_DECIMAL)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_Divide, KEY_DIVIDE)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_0, KEY_NUMPAD0)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_1, KEY_NUMPAD1)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_2, KEY_NUMPAD2)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_3, KEY_NUMPAD3)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_4, KEY_NUMPAD4)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_5, KEY_NUMPAD5)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_6, KEY_NUMPAD6)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_7, KEY_NUMPAD7)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_8, KEY_NUMPAD8)); + KeyMap.push_back(SKeyMap(XKB_KEY_KP_9, KEY_NUMPAD9)); KeyMap.push_back(SKeyMap(XKB_KEY_F1, KEY_F1)); KeyMap.push_back(SKeyMap(XKB_KEY_F2, KEY_F2)); KeyMap.push_back(SKeyMap(XKB_KEY_F3, KEY_F3)); From d2ececacf42c33f944526b1273f836b214326781 Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Sun, 1 Feb 2015 19:23:05 +0100 Subject: [PATCH 004/378] Delay sync --- lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp index c22d81a0e..202029e7a 100644 --- a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp @@ -493,7 +493,6 @@ CIrrDeviceWayland::CIrrDeviceWayland(const SIrrlichtCreationParameters& param) wl_seat_add_listener(seat, &WaylandCallbacks::seat_listener, this); wl_output_add_listener(output, &WaylandCallbacks::output_listener, this); - wl_display_dispatch(display); // create keymap createKeyMap(); @@ -512,10 +511,11 @@ CIrrDeviceWayland::CIrrDeviceWayland(const SIrrlichtCreationParameters& param) // create driver createDriver(); - if (!VideoDriver) - return; + if (VideoDriver) + createGUIAndScene(); - createGUIAndScene(); + // Sync everything wayland before leaving + wl_display_dispatch(display); } From 1844ab2a7cc09e7e9e75fd311f5022e7b42f305c Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Sun, 1 Feb 2015 19:38:47 +0100 Subject: [PATCH 005/378] Fix attempt for void scene in non advanced pipeline --- src/graphics/render_geometry.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/graphics/render_geometry.cpp b/src/graphics/render_geometry.cpp index 82b10fab5..3a822a7e1 100644 --- a/src/graphics/render_geometry.cpp +++ b/src/graphics/render_geometry.cpp @@ -614,6 +614,9 @@ void IrrDriver::renderSolidSecondPass() glMakeTextureHandleResidentARB(DepthHandle); } + if (CVS->supportsIndirectInstancingRendering()) + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, SolidPassCmd::getInstance()->drawindirectcmd); + { ScopedGPUTimer Timer(getGPUTimer(Q_SOLID_PASS2)); From cbf9e03b412a2b954b750d68627b769cbd1fe579 Mon Sep 17 00:00:00 2001 From: Vincent Lejeune Date: Sun, 1 Feb 2015 19:43:43 +0100 Subject: [PATCH 006/378] honour vsync param --- lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp | 2 -- lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp b/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp index c2504855b..b3e3c3cca 100644 --- a/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp +++ b/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp @@ -715,8 +715,6 @@ bool COpenGLDriver::initDriver(CIrrDeviceWayland* device) genericDriverInit(); - // set vsync -// extGlSwapInterval(Params.Vsync ? 1 : 0); return true; } diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp index 202029e7a..71b8df075 100644 --- a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp @@ -608,6 +608,7 @@ void CIrrDeviceWayland::initEGL() os::Printer::log("eglMakeCurrent() error", "", ELL_ERROR); } video::useCoreContext = true; + eglSwapInterval(egl_display, CreationParams.Vsync ? 1 : 0); } bool CIrrDeviceWayland::createWindow() From af68dd90464333f8a704ffd16b5b6e5e6e468c65 Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 28 Oct 2015 22:28:12 +0100 Subject: [PATCH 007/378] Fixed compilation --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f29517102..c64383c61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,7 +175,7 @@ endif() if(UNIX OR MINGW) # if(USE_CPP2011) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x -lwayland-client -lwayland-egl -lEGL -lxkbcommon") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x") # endif() endif() @@ -184,6 +184,9 @@ find_package(OpenGL REQUIRED) include_directories(${OPENGL_INCLUDE_DIR}) if(UNIX AND NOT APPLE) + find_package(X11 REQUIRED) + include_directories(${X11_INCLUDE_DIR}) + if(USE_XRANDR) find_package(Xrandr REQUIRED) if(NOT XRANDR_FOUND) @@ -321,6 +324,7 @@ target_link_libraries(supertuxkart ) if(UNIX AND NOT APPLE) + target_link_libraries(supertuxkart ${X11_LIBRARIES}) if(USE_XRANDR) target_link_libraries(supertuxkart ${XRANDR_LIBRARIES}) else() @@ -330,6 +334,7 @@ if(UNIX AND NOT APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") target_link_libraries(supertuxkart "-fsanitize=address") endif() + target_link_libraries(supertuxkart wayland-client wayland-egl EGL xkbcommon) endif() if(APPLE) From e61d3bc0acd1e248a2f2761300caec95dd19b664 Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 28 Oct 2015 23:40:57 +0100 Subject: [PATCH 008/378] Fixed a crash on startup --- lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp b/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp index 70600dae1..dae074364 100644 --- a/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp +++ b/lib/irrlicht/source/Irrlicht/COpenGLDriver.cpp @@ -697,7 +697,6 @@ COpenGLDriver::COpenGLDriver(const SIrrlichtCreationParameters& params, #ifdef _DEBUG setDebugName("COpenGLDriver"); #endif - genericDriverInit(); } From 8572c5ef3b96993af1766584e55309a856daa491 Mon Sep 17 00:00:00 2001 From: Deve Date: Thu, 29 Oct 2015 00:16:00 +0100 Subject: [PATCH 009/378] Open window in fullscreen mode when it was set by user --- lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp index 71b8df075..cf131d3b1 100644 --- a/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp +++ b/lib/irrlicht/source/Irrlicht/cirrdevicewayland.cpp @@ -615,7 +615,19 @@ bool CIrrDeviceWayland::createWindow() { surface = wl_compositor_create_surface(compositor); shell_surface = wl_shell_get_shell_surface(shell, surface); - wl_shell_surface_set_toplevel(shell_surface); + + if (CreationParams.Fullscreen) + { + wl_shell_surface_set_fullscreen(shell_surface, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, + 0, output); + } + else + { + wl_shell_surface_set_toplevel(shell_surface); + } + + wl_display_flush(display); initEGL(); From 3144cd1595ac8680d0fa1e9f2790e07474975813 Mon Sep 17 00:00:00 2001 From: Deve Date: Sat, 28 Nov 2015 16:42:48 +0100 Subject: [PATCH 010/378] Implement recording video to avi file. It allows to easily record a video, which can be then for example attached to a bug report. It also allows to record promotion videos (eg. trailers), which should be more smooth than recorded using external application. Main disadventages are: - Constant fps in avi file - Atm. it's not possible to record sounds. I even don't know if it's possible at all (if we can easily get access to the sound buffer). I see about 20-25% fps drop during recording. I think that it's acceptable. But if needed, the fps can be increased by using separated thread for recording. Currently it uses jpeg compression with 70 quality. It can be easily tweaked to use higher/lower compression or even just uncompressed bitmaps. --- src/graphics/irr_driver.cpp | 86 +++++++++ src/graphics/irr_driver.hpp | 6 + src/utils/avi_writer.cpp | 374 ++++++++++++++++++++++++++++++++++++ src/utils/avi_writer.hpp | 178 +++++++++++++++++ src/utils/debug.cpp | 17 +- 5 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 src/utils/avi_writer.cpp create mode 100644 src/utils/avi_writer.hpp diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index e7362930d..9a7040223 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -130,6 +130,9 @@ IrrDriver::IrrDriver() m_boundingboxesviz = false; m_last_light_bucket_distance = 0; memset(object_count, 0, sizeof(object_count)); + + m_avi_writer = NULL; + m_request_recording = false; } // IrrDriver // ---------------------------------------------------------------------------- @@ -137,6 +140,8 @@ IrrDriver::IrrDriver() */ IrrDriver::~IrrDriver() { + stopRecording(); + // Note that we can not simply delete m_post_processing here: // m_post_processing uses a material that has a reference to // m_post_processing (for a callback). So when the material is @@ -2118,6 +2123,56 @@ void IrrDriver::doScreenShot() image->drop(); } // doScreenShot + +void IrrDriver::startRecording() +{ + if (m_avi_writer != NULL) + return; + + time_t rawtime; + time(&rawtime); + tm* timeInfo = localtime(&rawtime); + char time_buffer[256]; + sprintf(time_buffer, "%i.%02i.%02i_%02i.%02i.%02i", + timeInfo->tm_year + 1900, timeInfo->tm_mon+1, + timeInfo->tm_mday, timeInfo->tm_hour, + timeInfo->tm_min, timeInfo->tm_sec); + + std::string track_name = World::getWorld() != NULL ? + race_manager->getTrackName() : "menu"; + + std::string path = file_manager->getScreenshotDir() + track_name + "-" + + time_buffer + ".avi"; + + m_request_recording = true; + int msec_per_frame = 1000 / m_video_driver->getFPS(); + m_avi_writer = new AVIWriter(); + m_avi_writer->createFile(path, AVI_FORMAT_JPG, m_actual_screen_size.Width, + m_actual_screen_size.Height, msec_per_frame, 24, 70); + +} + +void IrrDriver::stopRecording() +{ + if (m_avi_writer == NULL) + return; + + m_avi_writer->closeFile(); + delete m_avi_writer; + m_avi_writer = NULL; + m_request_recording = false; + + RaceGUIBase* base = World::getWorld() ? World::getWorld()->getRaceGUI() + : NULL; + if (base) + { + std::string path = file_manager->getScreenshotDir(); + base->addMessage( + core::stringw(("Video saved in\n" + path).c_str()), + NULL, 2.0f, video::SColor(255,255,255,255), true, false); + } +} + // ---------------------------------------------------------------------------- /** Update, called once per frame. * \param dt Time since last update @@ -2207,6 +2262,37 @@ void IrrDriver::update(float dt) // menu. //if(World::getWorld() && World::getWorld()->isRacePhase()) // printRenderStats(); + + if (m_request_recording == true && m_avi_writer != NULL) + { + video::IImage* image = m_video_driver->createScreenShot(); + + if (image != NULL) + { + int bits = image->getBytesPerPixel(); + int w = image->getDimension().Width; + int h = image->getDimension().Height; + + unsigned char* img = (unsigned char*)image->lock(); + + int buf_length = bits * w * h; + unsigned char* img_jpg = new unsigned char[buf_length]; + + int len = m_avi_writer->bmpToJpg(img, img_jpg, buf_length, w, h, + bits, 70); + m_avi_writer->addImage(img_jpg, len); + + delete[] img_jpg; + + image->unlock(); + image->drop(); + + // update msec per frame using the fps during recording to get + // something closer to the real value + int msec_per_frame = 1000 / m_video_driver->getFPS(); + m_avi_writer->updateMsecPerFrame(msec_per_frame); + } + } } // update // ---------------------------------------------------------------------------- diff --git a/src/graphics/irr_driver.hpp b/src/graphics/irr_driver.hpp index 606d7e2fe..38f02fd2e 100644 --- a/src/graphics/irr_driver.hpp +++ b/src/graphics/irr_driver.hpp @@ -42,6 +42,7 @@ #include "graphics/wind.hpp" #include "io/file_manager.hpp" #include "utils/aligned_array.hpp" +#include "utils/avi_writer.hpp" #include "utils/no_copy.hpp" #include "utils/ptr_vector.hpp" #include "utils/vec3.hpp" @@ -306,6 +307,9 @@ private: float m_ssao_radius; float m_ssao_k; float m_ssao_sigma; + + bool m_request_recording; + AVIWriter* m_avi_writer; #ifdef DEBUG /** Used to visualise skeletons. */ @@ -429,6 +433,8 @@ public: void printRenderStats(); bool supportsSplatting(); void requestScreenshot(); + void startRecording(); + void stopRecording(); void setTextureErrorMessage(const std::string &error, const std::string &detail=""); void unsetTextureErrorMessage(); diff --git a/src/utils/avi_writer.cpp b/src/utils/avi_writer.cpp new file mode 100644 index 000000000..d7373a14c --- /dev/null +++ b/src/utils/avi_writer.cpp @@ -0,0 +1,374 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2015 Dawid Gan +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "avi_writer.hpp" + + +bool AVIWriter::addJUNKChunk(std::string str, unsigned int min_size) +{ + int size = str.size() < min_size ? min_size : str.size() + 1; + size = (size + 1) & 0xfffffffe; + + CHUNK chunk; + chunk.fcc = FOURCC('J', 'U', 'N', 'K'); + chunk.cb = size; + + char buffer[size]; + memset(buffer, '\0', size); + strcpy(buffer, str.c_str()); + + int num = fwrite(&chunk, 1, sizeof(chunk), m_file); + if (num != sizeof(chunk)) + goto error; + + num = fwrite(buffer, 1, size * sizeof(char), m_file); + if (num != size) + goto error; + + m_last_junk_chunk = ftell(m_file); + if (m_last_junk_chunk < 0) + goto error; + + return true; + +error: + closeFile(true); + return false; +} + +bool AVIWriter::addImage(unsigned char* buffer, int buf_size) +{ + if (m_file == NULL) + return false; + + int num = ftell(m_file); + if (num < 0) + goto error; + + if (m_total_frames >= MAX_FRAMES) + goto error; + + CHUNK chunk; + chunk.fcc = m_chunk_fcc; + chunk.cb = buf_size; + + m_index_table[m_total_frames].Offset = num; + m_index_table[m_total_frames].Length = chunk.cb; + m_index_table[m_total_frames].fcc = chunk.fcc; + + num = fwrite(&chunk, 1, sizeof(chunk), m_file); + if (num != sizeof(chunk)) + goto error; + + num = fwrite(buffer, 1, buf_size, m_file); + if (num != buf_size) + goto error; + + int fill_size; fill_size = (sizeof(chunk) + buf_size) & 0x00000001; + if (fill_size > 0) + { + uint32_t filler = 0; + num = fwrite(&filler, 1, fill_size, m_file); + if (num != fill_size) + goto error; + } + + m_stream_bytes += sizeof(chunk) + buf_size + fill_size; + m_total_frames++; + + num = ftell(m_file); + if (num < 0) + goto error; + + if (((num - m_last_junk_chunk) > 20000) && (!addJUNKChunk("", 1))) + goto error; + + // check if we reached the file size limit + if (num >= MAX_FILE_SIZE) + goto error; + + return true; + +error: + closeFile(true); + return false; +} + +bool AVIWriter::closeFile(bool delete_file) +{ + if (m_file == NULL) + return false; + + if (delete_file) + goto error; + + // add the index + int idx_start; idx_start = ftell(m_file); + if (idx_start < 0) + goto error; + + CHUNK chunk; + chunk.fcc = FOURCC('i', 'd', 'x', '1'); + chunk.cb = sizeof(AVIINDEXENTRY) * m_total_frames; + + int num; num = fwrite(&chunk, 1, sizeof(chunk), m_file); + if (num != sizeof(chunk)) + goto error; + + for (unsigned int i = 0; i < m_total_frames; i++) + { + AVIINDEXENTRY Index; + Index.ckid = m_index_table[i].fcc; + Index.dwFlags = AVIIF_KEYFRAME; + Index.dwChunkOffset = m_index_table[i].Offset; + Index.dwChunkLength = m_index_table[i].Length; + + num = fwrite(&Index, 1, sizeof(Index), m_file); + if (num != sizeof(Index)) + goto error; + } + + // update the header + if (m_total_frames > 0 && m_msec_per_frame > 0) + { + num = fseek(m_file, 0, SEEK_END); + if (num < 0) + goto error; + + int size; size = ftell(m_file); + if (size < 0) + goto error; + + num = fseek(m_file, 0, SEEK_SET); + if (num < 0) + goto error; + + m_avi_hdr.riff.cb = size - sizeof(m_avi_hdr.riff); + m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame; + m_avi_hdr.avih.dwMaxBytesPerSec = (uint32_t)(((m_stream_bytes / m_total_frames) * + m_format_hdr.strh.dwRate) / m_msec_per_frame + 0.5f); + m_avi_hdr.avih.dwTotalFrames = m_total_frames; + + num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file); + if (num != sizeof(m_avi_hdr)) + goto error; + + m_format_hdr.strh.dwScale = m_msec_per_frame; + m_format_hdr.strh.dwLength = m_total_frames; + + num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file); + if (num != sizeof(m_format_hdr)) + goto error; + } + + // update the movi section + m_movi_chunk.cb = idx_start - m_movi_start; + + num = fseek(m_file, m_movi_start - sizeof(m_movi_chunk), SEEK_SET); + if (num < 0) + goto error; + + num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file); + if (num != sizeof(m_movi_chunk)) + goto error; + + fclose(m_file); + m_file = NULL; + + return true; + +error: + fclose(m_file); + remove(m_filename.c_str()); + m_file = NULL; + return false; +} + +bool AVIWriter::createFile(std::string filename, AVIFormat avi_format, + int width, int height, unsigned int msec_per_frame, + int bits, int quality) +{ + if (m_file != NULL) + return false; + + if (width < 1 || height < 1) + return false; + + m_filename = filename; + m_stream_bytes = 0; + m_total_frames = 0; + m_msec_per_frame = msec_per_frame; + m_movi_start = 0; + m_last_junk_chunk = 0; + m_total_frames = 0; + + BitmapInfoHeader bitmap_hdr; + bitmap_hdr.biSize = sizeof(BitmapInfoHeader); + bitmap_hdr.biWidth = width; + bitmap_hdr.biHeight = height; + bitmap_hdr.biPlanes = 1; + bitmap_hdr.biBitCount = bits; + bitmap_hdr.biCompression = 0; + bitmap_hdr.biSizeImage = (width * height * 3 * bitmap_hdr.biPlanes); + bitmap_hdr.biXPelsPerMeter = 0; + bitmap_hdr.biYPelsPerMeter = 0; + bitmap_hdr.biClrUsed = 0; + bitmap_hdr.biClrImportant = 0; + + memset(&m_avi_hdr, '\0', sizeof(m_avi_hdr)); + m_avi_hdr.riff.fcc = FOURCC('R', 'I', 'F', 'F'); + m_avi_hdr.riff.cb = 0; // update when finished (size of the file - 8) + m_avi_hdr.avi = FOURCC('A', 'V', 'I', ' '); + m_avi_hdr.list1.fcc = FOURCC('L', 'I', 'S', 'T'); + m_avi_hdr.list1.cb = 0; + m_avi_hdr.hdrl = FOURCC('h', 'd', 'r', 'l'); + m_avi_hdr.avihhdr.fcc = FOURCC('a', 'v', 'i', 'h'); + m_avi_hdr.avihhdr.cb = sizeof(m_avi_hdr.avih); + m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame; + m_avi_hdr.avih.dwMaxBytesPerSec = 0; // update when finished + m_avi_hdr.avih.dwPaddingGranularity = 0; + m_avi_hdr.avih.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX; + m_avi_hdr.avih.dwTotalFrames = 0; // update when finished + m_avi_hdr.avih.dwInitialFrames = 0; + m_avi_hdr.avih.dwStreams = 1; // 1 = video, 2 = video and audio + m_avi_hdr.avih.dwSuggestedBufferSize = 0; // can be just 0 + m_avi_hdr.avih.dwWidth = width; + m_avi_hdr.avih.dwHeight = height; + + m_format_hdr.list.fcc = FOURCC('L', 'I', 'S', 'T'); + m_format_hdr.list.cb = (sizeof(m_format_hdr) - 8) + sizeof(BitmapInfoHeader); + m_format_hdr.strl = FOURCC('s', 't', 'r', 'l'); + m_format_hdr.strhhdr.fcc = FOURCC('s', 't', 'r', 'h'); + m_format_hdr.strhhdr.cb = sizeof(m_format_hdr.strh); + m_format_hdr.strh.fccType = FOURCC('v', 'i', 'd', 's'); + m_format_hdr.strh.fccHandler = CC_DIB; + m_format_hdr.strh.dwFlags = 0; + m_format_hdr.strh.wPriority = 0; + m_format_hdr.strh.wLanguage = 0; + m_format_hdr.strh.dwInitialFrames = 0; + m_format_hdr.strh.dwScale = m_msec_per_frame; + m_format_hdr.strh.dwRate = 1000; + m_format_hdr.strh.dwStart = 0; + m_format_hdr.strh.dwLength = 0; // update when finished + m_format_hdr.strh.dwSuggestedBufferSize = 0; // can be just 0 + m_format_hdr.strh.dwQuality = quality * 100; + m_format_hdr.strh.dwSampleSize = 0; + m_format_hdr.strh.Left = 0; + m_format_hdr.strh.Top = 0; + m_format_hdr.strh.Right = m_avi_hdr.avih.dwWidth; + m_format_hdr.strh.Bottom = m_avi_hdr.avih.dwHeight; + m_format_hdr.strfhdr.fcc = FOURCC('s', 't', 'r', 'f'); + m_format_hdr.strfhdr.cb = sizeof(BitmapInfoHeader); + + //Format specific changes + if (avi_format == AVI_FORMAT_JPG) + { + m_format_hdr.strh.fccHandler = CC_MJPG; + bitmap_hdr.biCompression = FOURCC('M', 'J', 'P', 'G'); + m_chunk_fcc = FOURCC('0', '0', 'd', 'c'); + } + else if (avi_format == AVI_FORMAT_BMP) + { + bitmap_hdr.biHeight = -height; + bitmap_hdr.biCompression = 0; + m_chunk_fcc = FOURCC('0', '0', 'd', 'b'); + } + + const uint32_t fcc_movi = FOURCC('m', 'o', 'v', 'i'); + + m_file = fopen(filename.c_str(), "wb"); + if (m_file == NULL) + return false; + + int num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file); + if (num != sizeof(m_avi_hdr)) + goto error; + + num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file); + if (num != sizeof(m_format_hdr)) + goto error; + + num = fwrite(&bitmap_hdr, 1, sizeof(BitmapInfoHeader), m_file); + if (num != sizeof(BitmapInfoHeader)) + goto error; + + m_end_of_header = ftell(m_file); + if (m_end_of_header < 0) + goto error; + + if (!addJUNKChunk("", 2840)) + goto error; + + m_avi_hdr.list1.cb = m_end_of_header - sizeof(m_avi_hdr.riff) - + sizeof(m_avi_hdr.avi) - sizeof(m_avi_hdr.list1); + m_movi_chunk.fcc = FOURCC('L', 'I', 'S', 'T'); + m_movi_chunk.cb = 0; // update when finished + + num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file); + if (num != sizeof(m_movi_chunk)) + goto error; + + m_movi_start = ftell(m_file); + if (m_movi_start < 0) + goto error; + + num = fwrite(&fcc_movi, 1, sizeof(fcc_movi), m_file); + if (num != sizeof(fcc_movi)) + goto error; + + return true; + +error: + closeFile(true); + return false; +} + +int AVIWriter::bmpToJpg(unsigned char* image_data, unsigned char* image_output, + unsigned long buf_length, unsigned int width, + unsigned int height, int num_channels, int quality) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + jpeg_create_compress(&cinfo); + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = num_channels; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, true); + + jpeg_mem_dest(&cinfo, &image_output, &buf_length); + + jpeg_start_compress(&cinfo, true); + + JSAMPROW jrow[1]; + while (cinfo.next_scanline < cinfo.image_height) + { + jrow[0] = &image_data[cinfo.next_scanline * width * num_channels]; + jpeg_write_scanlines(&cinfo, jrow, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return buf_length; +} diff --git a/src/utils/avi_writer.hpp b/src/utils/avi_writer.hpp new file mode 100644 index 000000000..bb1e727e4 --- /dev/null +++ b/src/utils/avi_writer.hpp @@ -0,0 +1,178 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2015 Dawid Gan +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include +#include + +#include + +#define FOURCC(a,b,c,d) ((uint32_t) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a))) + +const uint32_t CC_MJPG = FOURCC('m', 'j', 'p', 'g'); +const uint32_t CC_DIB = FOURCC('\0', '\0', '\0', '\0'); +const uint32_t CC_VIDS = FOURCC('v', 'i', 'd', 's'); + +const uint32_t AVIF_HASINDEX = 0x00000010; +const uint32_t AVIF_MUSTUSEINDEX = 0x00000020; +const uint32_t AVIF_ISINTERLEAVED = 0x00000100; +const uint32_t AVIF_TRUSTCKTYPE = 0x00000800; +const uint32_t AVIF_WASCAPTUREFILE = 0x00010000; +const uint32_t AVIF_COPYRIGHTED = 0x00020000; + +const uint32_t AVISF_DISABLED = 0x00000001; +const uint32_t AVISF_VIDEO_PALCHANGES = 0x00010000; + +const uint32_t AVIIF_LIST = 0x00000001; +const uint32_t AVIIF_KEYFRAME = 0x00000010; +const uint32_t AVIIF_FIRSTPART = 0x00000020; +const uint32_t AVIIF_LASTPART = 0x00000040; +const uint32_t AVIIF_MIDPART = 0x00000060; +const uint32_t AVIIF_NOTIME = 0x00000100; +const uint32_t AVIIF_COMPUSE = 0x0FFF0000; + +enum AVIFormat {AVI_FORMAT_BMP, AVI_FORMAT_JPG}; +const int MAX_FRAMES = 1000000; +const int MAX_FILE_SIZE = 2000000000; + +struct MainAVIHeader +{ + uint32_t dwMicroSecPerFrame; + uint32_t dwMaxBytesPerSec; + uint32_t dwPaddingGranularity; + uint32_t dwFlags; + uint32_t dwTotalFrames; + uint32_t dwInitialFrames; + uint32_t dwStreams; + uint32_t dwSuggestedBufferSize; + uint32_t dwWidth; + uint32_t dwHeight; + uint32_t dwReserved[4]; +}; + +struct AVIStreamHeader +{ + uint32_t fccType; + uint32_t fccHandler; + uint32_t dwFlags; + uint16_t wPriority; + uint16_t wLanguage; + uint32_t dwInitialFrames; + uint32_t dwScale; + uint32_t dwRate; + uint32_t dwStart; + uint32_t dwLength; + uint32_t dwSuggestedBufferSize; + uint32_t dwQuality; + uint32_t dwSampleSize; + uint16_t Left; + uint16_t Top; + uint16_t Right; + uint16_t Bottom; +}; + +struct BitmapInfoHeader +{ + uint32_t biSize; + uint32_t biWidth; + uint32_t biHeight; + uint16_t biPlanes; + uint16_t biBitCount; + uint32_t biCompression; + uint32_t biSizeImage; + uint32_t biXPelsPerMeter; + uint32_t biYPelsPerMeter; + uint32_t biClrUsed; + uint32_t biClrImportant; +}; + +struct AVIINDEXENTRY +{ + uint32_t ckid; + uint32_t dwFlags; + uint32_t dwChunkOffset; + uint32_t dwChunkLength; +}; + +struct CHUNK +{ + uint32_t fcc; + uint32_t cb; +}; + +struct AVIHeader +{ + CHUNK riff; + uint32_t avi; + CHUNK list1; + uint32_t hdrl; + CHUNK avihhdr; + MainAVIHeader avih; +}; + +struct FormatHeader +{ + CHUNK list; + uint32_t strl; + CHUNK strhhdr; + AVIStreamHeader strh; + CHUNK strfhdr; +}; + +struct IndexTable +{ + uint32_t Offset; + uint32_t Length; + uint32_t fcc; +}; + + +class AVIWriter +{ +private: + FILE* m_file; + std::string m_filename; + int m_last_junk_chunk; + int m_end_of_header; + int m_movi_start; + unsigned int m_msec_per_frame; + unsigned int m_stream_bytes; + unsigned int m_total_frames; + AVIHeader m_avi_hdr; + CHUNK m_movi_chunk; + FormatHeader m_format_hdr; + IndexTable m_index_table[MAX_FRAMES]; + uint32_t m_chunk_fcc; + + bool addJUNKChunk(std::string str, unsigned int min_size); + +public: + AVIWriter() {m_file = NULL;} + + bool addImage(unsigned char* buffer, int size); + bool closeFile(bool delete_file = false); + bool createFile(std::string filename, AVIFormat avi_format, int width, + int height, unsigned int msec_per_frame, int bits, + int quality); + + void updateMsecPerFrame(unsigned int value) + {m_msec_per_frame = (m_msec_per_frame + value) / 2;} + + int bmpToJpg(unsigned char* image_data, unsigned char* image_output, + unsigned long buf_length, unsigned int width, + unsigned int height, int num_channels, int quality); +}; diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 17544f18d..4498b2cec 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -107,7 +107,9 @@ enum DebugMenuCommand DEBUG_VISUAL_VALUES, DEBUG_PRINT_START_POS, DEBUG_ADJUST_LIGHTS, - DEBUG_SCRIPT_CONSOLE + DEBUG_SCRIPT_CONSOLE, + DEBUG_START_RECORDING, + DEBUG_STOP_RECORDING }; // DebugMenuCommand // ----------------------------------------------------------------------------- @@ -566,6 +568,14 @@ bool handleContextMenuAction(s32 cmdID) { ScriptingConsole* console = new ScriptingConsole(); } + else if (cmdID == DEBUG_START_RECORDING) + { + irr_driver->startRecording(); + } + else if (cmdID == DEBUG_STOP_RECORDING) + { + irr_driver->stopRecording(); + } return false; } @@ -642,6 +652,11 @@ bool onEvent(const SEvent &event) sub->addItem(L"Normal view (Ctrl + F2)", DEBUG_GUI_CAM_NORMAL); sub->addItem(L"Toggle smooth camera", DEBUG_GUI_CAM_SMOOTH); sub->addItem(L"Attach fps camera to kart", DEBUG_GUI_CAM_ATTACH); + + int idx = mnu->addItem(L"Recording >",-1,true, true); + sub = mnu->getSubMenu(idx); + sub->addItem(L"Start recording", DEBUG_START_RECORDING); + sub->addItem(L"Stop recording", DEBUG_STOP_RECORDING); mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES); From 7e7d4e120ad8afec7e64e3383b36c62feeec37b3 Mon Sep 17 00:00:00 2001 From: Deve Date: Sun, 29 Nov 2015 20:29:37 +0100 Subject: [PATCH 011/378] Handle errors during recording. --- src/graphics/irr_driver.cpp | 48 +++++++++++++++++++++++++++---------- src/graphics/irr_driver.hpp | 3 +-- src/utils/avi_writer.cpp | 18 ++++++++------ src/utils/avi_writer.hpp | 16 +++++++++++-- 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index 9a7040223..c745898f9 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -132,7 +132,6 @@ IrrDriver::IrrDriver() memset(object_count, 0, sizeof(object_count)); m_avi_writer = NULL; - m_request_recording = false; } // IrrDriver // ---------------------------------------------------------------------------- @@ -2144,32 +2143,42 @@ void IrrDriver::startRecording() std::string path = file_manager->getScreenshotDir() + track_name + "-" + time_buffer + ".avi"; - m_request_recording = true; int msec_per_frame = 1000 / m_video_driver->getFPS(); m_avi_writer = new AVIWriter(); - m_avi_writer->createFile(path, AVI_FORMAT_JPG, m_actual_screen_size.Width, - m_actual_screen_size.Height, msec_per_frame, 24, 70); + bool success = m_avi_writer->createFile(path, AVI_FORMAT_JPG, + m_actual_screen_size.Width, + m_actual_screen_size.Height, + msec_per_frame, 24, 70); + + if (!success) + { + stopRecording(false); + } } -void IrrDriver::stopRecording() +void IrrDriver::stopRecording(bool success) { if (m_avi_writer == NULL) return; - m_avi_writer->closeFile(); + success = success && m_avi_writer->closeFile(); delete m_avi_writer; m_avi_writer = NULL; - m_request_recording = false; RaceGUIBase* base = World::getWorld() ? World::getWorld()->getRaceGUI() : NULL; if (base) { - std::string path = file_manager->getScreenshotDir(); - base->addMessage( - core::stringw(("Video saved in\n" + path).c_str()), - NULL, 2.0f, video::SColor(255,255,255,255), true, false); + std::string message; + + if (success) + message = "Video saved in\n" + file_manager->getScreenshotDir(); + else + message = "Recording failed."; + + base->addMessage(core::stringw(message.c_str()), NULL, 2.0f, + video::SColor(255,255,255,255), true, false); } } @@ -2263,7 +2272,7 @@ void IrrDriver::update(float dt) //if(World::getWorld() && World::getWorld()->isRacePhase()) // printRenderStats(); - if (m_request_recording == true && m_avi_writer != NULL) + if (m_avi_writer != NULL) { video::IImage* image = m_video_driver->createScreenShot(); @@ -2280,7 +2289,7 @@ void IrrDriver::update(float dt) int len = m_avi_writer->bmpToJpg(img, img_jpg, buf_length, w, h, bits, 70); - m_avi_writer->addImage(img_jpg, len); + AVIErrCode result = m_avi_writer->addImage(img_jpg, len); delete[] img_jpg; @@ -2291,6 +2300,19 @@ void IrrDriver::update(float dt) // something closer to the real value int msec_per_frame = 1000 / m_video_driver->getFPS(); m_avi_writer->updateMsecPerFrame(msec_per_frame); + + if (result == AVI_SIZE_LIMIT_ERR) + { + // File size limit reached. Starting new file... + delete m_avi_writer; + m_avi_writer = NULL; + + startRecording(); + } + else if (result == AVI_IO_ERR) + { + stopRecording(false); + } } } } // update diff --git a/src/graphics/irr_driver.hpp b/src/graphics/irr_driver.hpp index 38f02fd2e..1f420d247 100644 --- a/src/graphics/irr_driver.hpp +++ b/src/graphics/irr_driver.hpp @@ -308,7 +308,6 @@ private: float m_ssao_k; float m_ssao_sigma; - bool m_request_recording; AVIWriter* m_avi_writer; #ifdef DEBUG @@ -434,7 +433,7 @@ public: bool supportsSplatting(); void requestScreenshot(); void startRecording(); - void stopRecording(); + void stopRecording(bool success = true); void setTextureErrorMessage(const std::string &error, const std::string &detail=""); void unsetTextureErrorMessage(); diff --git a/src/utils/avi_writer.cpp b/src/utils/avi_writer.cpp index d7373a14c..81565ee86 100644 --- a/src/utils/avi_writer.cpp +++ b/src/utils/avi_writer.cpp @@ -51,17 +51,17 @@ error: return false; } -bool AVIWriter::addImage(unsigned char* buffer, int buf_size) +AVIErrCode AVIWriter::addImage(unsigned char* buffer, int buf_size) { if (m_file == NULL) - return false; + goto error; - int num = ftell(m_file); + int num; num = ftell(m_file); if (num < 0) goto error; if (m_total_frames >= MAX_FRAMES) - goto error; + goto size_limit; CHUNK chunk; chunk.fcc = m_chunk_fcc; @@ -100,13 +100,17 @@ bool AVIWriter::addImage(unsigned char* buffer, int buf_size) // check if we reached the file size limit if (num >= MAX_FILE_SIZE) - goto error; + goto size_limit; - return true; + return AVI_SUCCESS; error: closeFile(true); - return false; + return AVI_IO_ERR; + +size_limit: + closeFile(); + return AVI_SIZE_LIMIT_ERR; } bool AVIWriter::closeFile(bool delete_file) diff --git a/src/utils/avi_writer.hpp b/src/utils/avi_writer.hpp index bb1e727e4..f881ed95c 100644 --- a/src/utils/avi_writer.hpp +++ b/src/utils/avi_writer.hpp @@ -45,7 +45,19 @@ const uint32_t AVIIF_MIDPART = 0x00000060; const uint32_t AVIIF_NOTIME = 0x00000100; const uint32_t AVIIF_COMPUSE = 0x0FFF0000; -enum AVIFormat {AVI_FORMAT_BMP, AVI_FORMAT_JPG}; +enum AVIFormat +{ + AVI_FORMAT_BMP, + AVI_FORMAT_JPG +}; + +enum AVIErrCode +{ + AVI_SUCCESS, + AVI_SIZE_LIMIT_ERR, + AVI_IO_ERR +}; + const int MAX_FRAMES = 1000000; const int MAX_FILE_SIZE = 2000000000; @@ -163,7 +175,7 @@ private: public: AVIWriter() {m_file = NULL;} - bool addImage(unsigned char* buffer, int size); + AVIErrCode addImage(unsigned char* buffer, int size); bool closeFile(bool delete_file = false); bool createFile(std::string filename, AVIFormat avi_format, int width, int height, unsigned int msec_per_frame, int bits, From 5d6a233da2db502c6f7b5af4a833b35faeaf9782 Mon Sep 17 00:00:00 2001 From: Deve Date: Sun, 29 Nov 2015 20:31:10 +0100 Subject: [PATCH 012/378] Fixed wrong unit --- src/utils/avi_writer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/avi_writer.cpp b/src/utils/avi_writer.cpp index 81565ee86..2fe7f3a25 100644 --- a/src/utils/avi_writer.cpp +++ b/src/utils/avi_writer.cpp @@ -163,7 +163,7 @@ bool AVIWriter::closeFile(bool delete_file) goto error; m_avi_hdr.riff.cb = size - sizeof(m_avi_hdr.riff); - m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame; + m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame * 1000; m_avi_hdr.avih.dwMaxBytesPerSec = (uint32_t)(((m_stream_bytes / m_total_frames) * m_format_hdr.strh.dwRate) / m_msec_per_frame + 0.5f); m_avi_hdr.avih.dwTotalFrames = m_total_frames; @@ -243,7 +243,7 @@ bool AVIWriter::createFile(std::string filename, AVIFormat avi_format, m_avi_hdr.hdrl = FOURCC('h', 'd', 'r', 'l'); m_avi_hdr.avihhdr.fcc = FOURCC('a', 'v', 'i', 'h'); m_avi_hdr.avihhdr.cb = sizeof(m_avi_hdr.avih); - m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame; + m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame * 1000; m_avi_hdr.avih.dwMaxBytesPerSec = 0; // update when finished m_avi_hdr.avih.dwPaddingGranularity = 0; m_avi_hdr.avih.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX; From 803eba5d5c4195c879b365918035c9abfff1f978 Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 27 Jan 2017 22:43:49 +0100 Subject: [PATCH 013/378] Fixed typo --- .gitignore | 17 + android/Android.mk | 174 ++++ android/AndroidManifest.xml | 38 + android/README.ANDROID | 134 +++ android/build.xml | 6 + android/generate_assets.sh | 240 +++++ android/make.sh | 288 ++++++ android/res/drawable-hdpi/icon.png | Bin 0 -> 5874 bytes android/res/drawable-mdpi/icon.png | Bin 0 -> 3208 bytes android/res/drawable-xhdpi/icon.png | Bin 0 -> 9068 bytes android/res/drawable-xxhdpi/icon.png | Bin 0 -> 17129 bytes android/res/drawable/icon.png | Bin 0 -> 37650 bytes android/res/values/strings.xml | 4 + cmake/Toolchain-android.cmake | 32 + lib/irrlicht/CMakeLists.txt | 2 + .../source/Irrlicht/CIrrDeviceAndroid.cpp | 963 ++++++++++++++++++ .../source/Irrlicht/CIrrDeviceAndroid.h | 126 +++ lib/libpng/CMakeLists.txt | 1 + sources.cmake | 2 +- src/io/assets_android.cpp | 433 ++++++++ src/io/assets_android.hpp | 44 + src/main_android.cpp | 62 ++ src/utils/progress_bar_android.cpp | 205 ++++ src/utils/progress_bar_android.hpp | 55 + 24 files changed, 2825 insertions(+), 1 deletion(-) create mode 100644 android/Android.mk create mode 100644 android/AndroidManifest.xml create mode 100644 android/README.ANDROID create mode 100644 android/build.xml create mode 100755 android/generate_assets.sh create mode 100755 android/make.sh create mode 100644 android/res/drawable-hdpi/icon.png create mode 100644 android/res/drawable-mdpi/icon.png create mode 100644 android/res/drawable-xhdpi/icon.png create mode 100644 android/res/drawable-xxhdpi/icon.png create mode 100644 android/res/drawable/icon.png create mode 100644 android/res/values/strings.xml create mode 100644 cmake/Toolchain-android.cmake create mode 100644 lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp create mode 100644 lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h create mode 100644 src/io/assets_android.cpp create mode 100644 src/io/assets_android.hpp create mode 100644 src/main_android.cpp create mode 100644 src/utils/progress_bar_android.cpp create mode 100644 src/utils/progress_bar_android.hpp diff --git a/.gitignore b/.gitignore index 4f08c5f41..972670fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,20 @@ packets_log.txt history.dat README.dependencies xx + +android/android-ndk* +android/android-sdk* +android/assets +android/bin +android/obj +android/libs +android-* +*.apk + +lib/curl +lib/freetype +lib/ifaddrs +lib/libogg +lib/libvorbis +lib/openal +lib/openssl diff --git a/android/Android.mk b/android/Android.mk new file mode 100644 index 000000000..08728da5f --- /dev/null +++ b/android/Android.mk @@ -0,0 +1,174 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + + +# OpenAL +LOCAL_MODULE := openal +LOCAL_SRC_FILES := obj/openal/libopenal.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# OGG +LOCAL_MODULE := ogg +LOCAL_SRC_FILES := obj/libogg/src/.libs/libogg.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Vorbis +LOCAL_MODULE := vorbis +LOCAL_SRC_FILES := obj/libvorbis/lib/.libs/libvorbis.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Vorbisfile +LOCAL_MODULE := vorbisfile +LOCAL_SRC_FILES := obj/libvorbis/lib/.libs/libvorbisfile.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# CURL +LOCAL_MODULE := curl +LOCAL_SRC_FILES := obj/curl/lib/.libs/libcurl.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# libcrypto +LOCAL_MODULE := libcrypto +LOCAL_SRC_FILES := obj/openssl/libcrypto.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# libssl +LOCAL_MODULE := libssl +LOCAL_SRC_FILES := obj/openssl/libssl.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# JPEG +LOCAL_MODULE := jpeglib +LOCAL_SRC_FILES := obj/jpeglib/libjpeglib.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Freetype +LOCAL_MODULE := freetype +LOCAL_SRC_FILES := obj/freetype/objs/.libs/libfreetype.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# zlib +LOCAL_MODULE := zlib +LOCAL_SRC_FILES := obj/zlib/libz.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# PNG +LOCAL_MODULE := png +LOCAL_SRC_FILES := obj/libpng/libpng.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# ifaddrs +LOCAL_MODULE := ifaddrs +LOCAL_PATH := . +LOCAL_SRC_FILES := ../lib/ifaddrs/ifaddrs.c +LOCAL_CFLAGS := -I../lib/ifaddrs +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# AngelScript +LOCAL_MODULE := angelscript +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti exceptions +LOCAL_SRC_FILES := $(wildcard ../lib/angelscript/source/*.S) \ + $(wildcard ../lib/angelscript/source/*.cpp) +LOCAL_CFLAGS := -I../lib/angelscript/source/ +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# ENET +LOCAL_MODULE := enet +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti +LOCAL_SRC_FILES := $(wildcard ../lib/enet/*.c) +LOCAL_CFLAGS := -I../lib/enet/include/ -DHAS_SOCKLEN_T +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Bullet +LOCAL_MODULE := bullet +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti +LOCAL_SRC_FILES := $(wildcard ../lib/bullet/src/*/*.cpp) \ + $(wildcard ../lib/bullet/src/*/*/*.cpp) +LOCAL_CFLAGS := -I../lib/bullet/src/ +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Irrlicht +LOCAL_MODULE := irrlicht +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti +LOCAL_SRC_FILES := $(wildcard ../lib/irrlicht/source/Irrlicht/*.cpp) \ + $(wildcard ../lib/irrlicht/source/Irrlicht/Android/*.cpp) +LOCAL_CFLAGS := -I../lib/irrlicht/source/Irrlicht/ \ + -I../lib/irrlicht/include/ \ + -Iobj/jpeglib/ \ + -Iobj/libpng/ \ + -Iobj/zlib/ \ + -I$(call my-dir)/../../sources/android/native_app_glue + -std=gnu++0x +LOCAL_STATIC_LIBRARIES := jpeglib png zlib +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# STK +LOCAL_MODULE := main +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti exceptions +LOCAL_SRC_FILES := $(wildcard ../src/*.cpp) \ + $(wildcard ../src/*/*.cpp) \ + $(wildcard ../src/*/*/*.cpp) +LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv3 -lOpenSLES +LOCAL_CFLAGS := -I../lib/angelscript/include \ + -I../lib/bullet/src \ + -I../lib/enet/include \ + -I../lib/ifaddrs \ + -I../lib/irrlicht/include \ + -I../lib/irrlicht/source/Irrlicht \ + -I../src \ + -Iobj/curl/include \ + -Iobj/freetype/include \ + -Iobj/libogg/include \ + -Iobj/libvorbis/include \ + -Iobj/openal/include \ + -I$(call my-dir)/../../sources/android/native_app_glue \ + -DUSE_GLES2 \ + -DHAVE_OGGVORBIS \ + -DNDEBUG \ + -std=gnu++0x + +LOCAL_STATIC_LIBRARIES := irrlicht bullet enet freetype ifaddrs angelscript \ + vorbisfile vorbis ogg openal curl libssl libcrypto \ + gnustl_static android_native_app_glue + +include $(BUILD_SHARED_LIBRARY) +include $(CLEAR_VARS) + +$(call import-module,android/native_app_glue) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 000000000..1a6a1f61d --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/README.ANDROID b/android/README.ANDROID new file mode 100644 index 000000000..85903d41d --- /dev/null +++ b/android/README.ANDROID @@ -0,0 +1,134 @@ +================================================================================ + + SUPERTUXKART + +================================================================================ + + + +-------------------------------------------------------------------------------- + SYSTEM REQUIREMENTS +-------------------------------------------------------------------------------- + +To run SuperTuxKart on Android, you need a device that meets following +requirements: + +- Android 4.4 or later +- Processor compatible with armv7 or x86 +- GPU that supports OpenGL ES 3.0 +- 1 GB RAM (STK uses ~150 MB in minimal configuration) +- 300 MB of free space on internal storage +- Touch screen or external keyboard + + + +-------------------------------------------------------------------------------- + COMPILATION +-------------------------------------------------------------------------------- + +The build scripts are designed to run under linux. They may work under cygwin +after some tweaks, but atm. only linux is supported. + +Dependencies list (may be incomplete): + + autoconf, automake, make, python, ant, imagemagick, cmake, vorbis-tools + +Additionally some dependencies for optimize_data script: + + advancecomp, libjpeg-progs, optipng + +Before compilation you must download the package with dependencies from: +https://github.com/supertuxkart/dependencies +and extract it to stk-code/lib. It contains sources of libraries that are used +in STK, but are not availiable in stk-code repository (curl, freetype, openal). + +You need also Android SDK for android-19 platform (the API for Android 4.4) and +Android NDK (versions r12b and r13b have been tested). + +You need to create proper "android-sdk" and "android-ndk" symlinks in the +directory with Android project, so that the compilation script will have access +to the SDK and NDK. + +These paths can be also set in SDK_PATH and NDK_PATH environmental variables. + +Before running the compilation, run the generate_assets script, so that +selected assets will be copied to "assets" directory, and then included in the +apk file. + +You can select different karts and tracks by setting KARTS and TRACKS variables +in the generate_assets.sh script at the beginning of file. + +When you are creating the assets directory manually, note that the +directories.txt file is urgently needed and it is used by the application for +extracting assets. + +If the assets directory is already prepared, you can run "./make.sh" command to +build the project and create an apk file. Note that all arguments are passed to +the make command, so that you can run "./make.sh -j5" for multi-threaded build. + +If you want to prepare a package for particular architecture, you can choose it +by setting the COMPILE_ARCH environmental variable. At this stage, supported +architectures are "armv7", "x86" and "aarch64". The default is "armv7". + +Basically if all dependencies are installed in the system, it should be enough +to just run: + + export SDK_PATH=/path/to/your/android/sdk + export NDK_PATH=/path/to/your/android/ndk + ./generate_assets.sh + ./make.sh -j5 + + + +-------------------------------------------------------------------------------- + KNOWN ISSUES +-------------------------------------------------------------------------------- + +1. At this stage only shader-based (OpenGL ES 3.0) pipeline works. The fixed + pipeline (GLES 2.0) could work (it works under linux), but it doesn't look + good and is generally broken. It means that it's not possible to run STK on + Android 4.2 or older. It is technically possible to do - check GLES context + version, load OpenGL functions dynamically using EGL, and if they are not + loaded properly, then fallback to GLES 2.0. But these devices may be too + slow to run STK anyway. + +2. It never ocurred for me, but it's possible that EGL context is lost in some + cases. SuperTuxKart is not designed to re-create all textures at any moment, + so this is a "Wontfix", at least for now. + +3. Some bright tracks (Farm, Gran Paradiso) seem to be a bit darker in GLES + renderer than in original OpenGL 3.x renderer. It can be easily hacked by + adding few lines to object pass shader, but we should rather try to find the + real reason. + +4. Explosion effect has poor performance and causes fps drop for a while. + Though it can be easily tweaked, so that less particles per second is + generated. + +5. Touch steering needs nice button icons. + +6. We use "exit(0)" at the end of main function. We shouldn't do it and we + should just return from the main function. But STK uses some global + variables and their values are remembered when the game is restarted. We + should properly clear them or re-initialize on startup. Using the "exit(0)" + is not-that-bad workaround, but it may cause a crash on exit sometimes. + It seems to affect only Android 5.0. More information about the crash: + https://code.google.com/p/android/issues/detail?id=160824 + +7. STK crashes on Qualcomm with Adreno 305 when trying to draw menu interface. + Backtrace shows glDrawElements function, and internally crashed in vbo + allocation. + +8. STK crashes on startup on some devices when aarch64 build is made using + Android r13 NDK. The r13 version has rather big modifications (it uses clang + instead of gcc by default). This is probably a bug in NDK/compiler/OS, but + for this reason using NDK r12 for 64-bit arm compilation is preferred. + +9. Angelscript doesn't have full support for aarch64 builds, so that scripting + won't work on this platform. + +10. Steering with accelerometer is not available yet. It needs some work to do + it properly because tablets have different screen orientation than phones, + so that they receive events from different axies during rotating the device. + As far as I see it's not possible to read default screen orientation using + NDK functions. diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 000000000..54c3d58b4 --- /dev/null +++ b/android/build.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/generate_assets.sh b/android/generate_assets.sh new file mode 100755 index 000000000..2fdec9214 --- /dev/null +++ b/android/generate_assets.sh @@ -0,0 +1,240 @@ +#!/bin/sh +# +# (C) 2016-2017 Dawid Gan, under the GPLv3 +# +# A script that generates data files for Android apk + + +# Below are simple configuration variables +# It's allowed to set "all" for KARTS and TRACKS if it's intended to create +# package with full data. +# The karts and tracks directories shouldn't exist in ASSETS_DIRS variable +# because they are handled separately +# The TEXTURE_SIZE and SOUND_QUALITY take effect only if DECREASE_QUALITY has +# value greater than 0 +# The script needs imagemagick and ogg utils installed to use DECREASE_QUALITY +# feature + +################################################################################ + +export KARTS="tux nolok xue" +export TRACKS="battleisland cornfield_crossing featunlocked gplose gpwin \ + hacienda introcutscene introcutscene2 lighthouse olivermath \ + overworld snowmountain snowtuxpeak soccer_field tutorial" + +export ASSETS_PATHS="../data \ + ../../stk-assets \ + ../../supertuxkart-assets" + +export ASSETS_DIRS="library models music sfx textures" + +export TEXTURE_SIZE=256 +export SOUND_QUALITY=64 + +export RUN_OPTIMIZE_SCRIPT=0 +export DECREASE_QUALITY=1 + +################################################################################ + + +cd "`dirname "$0"`" + +# Find assets path +for ASSETS_PATH in $ASSETS_PATHS; do + if [ -d $ASSETS_PATH ] && [ `ls $ASSETS_PATH | grep -c tracks` -gt 0 ]; then + echo "Assets found in $ASSETS_PATH" + ASSETS_PATH_FOUND=1 + break + fi +done + +if [ -z $ASSETS_PATH_FOUND ]; then + echo "Couldn't find assets path" + exit 1 +fi + +if [ ! -d "../data" ]; then + echo "Couldn't find data directory" + exit 1 +fi + + +# Clear previous assets directory +echo "Clear previous assets directory" +rm -rf assets + + +# Copy all assets +echo "Copy all assets" + +mkdir -p assets/data + +for DIR in `ls $ASSETS_PATH`; do + CAN_BE_COPIED=0 + + for ASSETS_DIR in $ASSETS_DIRS; do + if [ $DIR = $ASSETS_DIR ]; then + CAN_BE_COPIED=1 + break + fi + done; + + # Don't copy karts and tracks. It will be handled later + BLACKLIST_ASSETS="karts tracks" + for ASSETS_DIR in $BLACKLIST_ASSETS; do + if [ $DIR = $ASSETS_DIR ]; then + CAN_BE_COPIED=0 + break + fi + done; + + if [ $CAN_BE_COPIED -gt 0 ]; then + cp -a "$ASSETS_PATH/$DIR" assets/data/ + fi +done; + + +# Copy selected tracks +echo "Copy selected tracks" + +mkdir -p assets/data/tracks + +for DIR in `ls $ASSETS_PATH/tracks`; do + CAN_BE_COPIED=0 + + if [ "$TRACKS" != "all" ]; then + for TRACK in $TRACKS; do + if [ $DIR = $TRACK ]; then + CAN_BE_COPIED=1 + break + fi + done; + else + CAN_BE_COPIED=1 + fi + + if [ $CAN_BE_COPIED -gt 0 ]; then + cp -a "$ASSETS_PATH/tracks/$DIR" assets/data/tracks/ + fi +done + + +# Copy selected karts +echo "Copy selected karts" + +mkdir -p assets/data/karts + +for DIR in `ls $ASSETS_PATH/karts`; do + CAN_BE_COPIED=0 + + if [ "$KARTS" != "all" ]; then + for KART in $KARTS; do + if [ $DIR = $KART ]; then + CAN_BE_COPIED=1 + break + fi + done; + else + CAN_BE_COPIED=1 + fi + + if [ $CAN_BE_COPIED -gt 0 ]; then + cp -a "$ASSETS_PATH/karts/$DIR" assets/data/karts/ + fi +done + + +# Decrease assets quality in order to save some disk space and RAM +echo "Decrease assets quality" + +convert_image() +{ + if [ -z "$1" ]; then + echo "No file to convert" + return + fi + + FILE="$1" + + W=`identify -format "%[fx:w]" "$FILE"` + H=`identify -format "%[fx:h]" "$FILE"` + + if [ -z $W ] || [ -z $H ]; then + echo "Couldn't convert $FILE file" + return + fi + + if [ $W -le $TEXTURE_SIZE ] && [ $H -le $TEXTURE_SIZE ]; then + return + fi + + if [ $W -gt $H ]; then + SCALED_W=$TEXTURE_SIZE + SCALED_H=$(($TEXTURE_SIZE * $H / $W)) + else + SCALED_W=$(($TEXTURE_SIZE * $W / $H)) + SCALED_H=$TEXTURE_SIZE + fi + + convert -scale $SCALED_WE\x$SCALED_H "$FILE" "$FILE" +} + +convert_sound() +{ + if [ -z "$1" ]; then + echo "No file to convert" + return + fi + + FILE="$1" + + oggdec "$FILE" -o tmp.wav + + if [ -s tmp.wav ]; then + oggenc --downmix -b $SOUND_QUALITY tmp.wav -o tmp.ogg + fi + + if [ -s tmp.ogg ]; then + SIZE_OLD=`du -k "$FILE" | cut -f1` + SIZE_NEW=`du -k "tmp.ogg" | cut -f1` + + if [ $SIZE_NEW -lt $SIZE_OLD ]; then + mv tmp.ogg "$FILE" + fi + fi + + rm -f tmp.wav tmp.ogg +} + +if [ $DECREASE_QUALITY -gt 0 ]; then + find assets/data -iname "*.png" | while read f; do convert_image "$f"; done + find assets/data -iname "*.jpg" | while read f; do convert_image "$f"; done + find assets/data -iname "*.ogg" | while read f; do convert_sound "$f"; done +fi + + +# Copy data directory +echo "Copy data directory" +cp -a ../data/* assets/data/ + + +# Run optimize_data.sh script +if [ $RUN_OPTIMIZE_SCRIPT -gt 0 ]; then + echo "Run optimize_data.sh script" + sh -c 'cd assets/data; ../../../data/optimize_data.sh' +fi + + +# Generate directories list +echo "Generate directories list" +find assets/* -type d > assets/directories.txt +sed -i s/'.\/assets\/'// assets/directories.txt +sed -i s/'assets\/'// assets/directories.txt + + +# It will be probably ignored by ant, but create it anyway... +touch assets/.nomedia + + +echo "Done." +exit 0 diff --git a/android/make.sh b/android/make.sh new file mode 100755 index 000000000..ee7971e0e --- /dev/null +++ b/android/make.sh @@ -0,0 +1,288 @@ +#!/bin/sh +# +# (C) 2016-2017 Dawid Gan, under the GPLv3 +# +# A script that creates the apk build + + +export DIRNAME=$(realpath "$(dirname "$0")") + +export NDK_PATH_DEFAULT="$DIRNAME/android-ndk" +export SDK_PATH_DEFAULT="$DIRNAME/android-sdk" + +export NDK_TOOLCHAIN_PATH="$DIRNAME/obj/bin" +export NDK_BUILD_SCRIPT="$DIRNAME/Android.mk" +export PATH="$DIRNAME/obj/bin:$PATH" +export CROSS_SYSROOT="$DIRNAME/obj/sysroot" + +#export NDK_CCACHE=ccache +export NDK_CPPFLAGS="-O3 -g" + +export NDK_ABI_ARMV7=armeabi-v7a +export ARCH_ARMV7=arm +export HOST_ARMV7=arm-linux-androideabi +export NDK_PLATFORM_ARMV7=android-19 + +export NDK_ABI_X86=x86 +export ARCH_X86=x86 +export HOST_X86=i686-linux-android +export NDK_PLATFORM_X86=android-19 + +export NDK_ABI_AARCH64=arm64-v8a +export ARCH_AARCH64=arm64 +export HOST_AARCH64=aarch64-linux-android +export NDK_PLATFORM_AARCH64=android-21 + + +# A helper function that checks if error ocurred +check_error() +{ + if [ $? -gt 0 ]; then + echo "Error ocurred." + exit + fi +} + +# Handle clean command +if [ ! -z "$1" ] && [ "$1" = "clean" ]; then + rm -rf bin + rm -rf libs + rm -rf obj + exit +fi + +# Check if compilation for different platform has been started before +if [ -f "$DIRNAME/obj/compile_arch" ]; then + PROJECT_ARCH=$(cat "$DIRNAME/obj/compile_arch") + + if [ -z "$COMPILE_ARCH" ]; then + COMPILE_ARCH="$PROJECT_ARCH" + elif [ "$PROJECT_ARCH" != "$COMPILE_ARCH" ]; then + echo "Error: Compilation for different platform has been already made." + echo "Run './make.sh clean' first or set COMPILE_ARCH variable" \ + "to '$PROJECT_ARCH.'" + exit + fi +fi + +if [ -z "$COMPILE_ARCH" ]; then + COMPILE_ARCH="armv7" +fi + +# Update variables for selected architecture +if [ "$COMPILE_ARCH" = "armv7" ]; then + export NDK_PLATFORM=$NDK_PLATFORM_ARMV7 + export NDK_ABI=$NDK_ABI_ARMV7 + export ARCH=$ARCH_ARMV7 + export HOST=$HOST_ARMV7 +elif [ "$COMPILE_ARCH" = "x86" ]; then + export NDK_PLATFORM=$NDK_PLATFORM_X86 + export NDK_ABI=$NDK_ABI_X86 + export ARCH=$ARCH_X86 + export HOST=$HOST_X86 +elif [ "$COMPILE_ARCH" = "aarch64" ]; then + export NDK_PLATFORM=$NDK_PLATFORM_AARCH64 + export NDK_ABI=$NDK_ABI_AARCH64 + export ARCH=$ARCH_AARCH64 + export HOST=$HOST_AARCH64 +else + echo "Unknow COMPILE_ARCH: $COMPILE_ARCH. Possible values are: " \ + "armv7, aarch64, x86" + exit +fi + +# Check if we have access to the Android NDK and SDK +if [ -z "$NDK_PATH" ]; then + export NDK_PATH="$NDK_PATH_DEFAULT" +fi + +if [ -z "$SDK_PATH" ]; then + export SDK_PATH="$SDK_PATH_DEFAULT" +fi + +NDK_PATH=$(realpath "$NDK_PATH") +SDK_PATH=$(realpath "$SDK_PATH") + +if [ ! -d "$NDK_PATH" ]; then + echo "Error: Couldn't find $NDK_PATH directory. Please create a symlink" \ + "to your Android NDK installation in the $NDK_PATH_DEFAULT or set" \ + "proper path in the NDK_PATH variable" + exit +fi + +if [ ! -d "$SDK_PATH" ]; then + echo "Error: Couldn't find $SDK_PATH directory. Please create a symlink" \ + "to your Android SDK installation in the $SDK_PATH_DEFAULT or set" \ + "proper path in the SDK_PATH variable" + exit +fi + +# Standalone toolchain +if [ ! -f "$DIRNAME/obj/make_standalone_toolchain.stamp" ]; then + echo "Creating standalone toolchain" + rm -rf "$DIRNAME/obj" + ${NDK_PATH}/build/tools/make-standalone-toolchain.sh \ + --platform=$NDK_PLATFORM \ + --install-dir="$DIRNAME/obj/" \ + --arch=$ARCH + check_error + touch "$DIRNAME/obj/make_standalone_toolchain.stamp" + echo $COMPILE_ARCH > "$DIRNAME/obj/compile_arch" +fi + +# Freetype +if [ ! -f "$DIRNAME/obj/freetype.stamp" ]; then + echo "Compiling freetype" + mkdir -p "$DIRNAME/obj/freetype" + cp -a -f "$DIRNAME/../lib/freetype/"* "$DIRNAME/obj/freetype" + + cd "$DIRNAME/obj/freetype" + ./configure --host=$HOST \ + --without-zlib \ + --without-png \ + --without-harfbuzz && + make $@ + check_error + touch "$DIRNAME/obj/freetype.stamp" +fi + +# Zlib +if [ ! -f "$DIRNAME/obj/zlib.stamp" ]; then + echo "Compiling zlib" + mkdir -p "$DIRNAME/obj/zlib" + cp -a -f "$DIRNAME/../lib/zlib/"* "$DIRNAME/obj/zlib" + + cd "$DIRNAME/obj/zlib" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/zlib.stamp" +fi + +# Libpng +if [ ! -f "$DIRNAME/obj/libpng.stamp" ]; then + echo "Compiling libpng" + mkdir -p "$DIRNAME/obj/libpng" + mkdir -p "$DIRNAME/obj/libpng/lib" + cp -a -f "$DIRNAME/../lib/libpng/"* "$DIRNAME/obj/libpng" + + cd "$DIRNAME/obj/libpng" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST \ + -DZLIB_LIBRARY="$DIRNAME/obj/zlib/libz.a" \ + -DZLIB_INCLUDE_DIR="$DIRNAME/obj/zlib/" \ + -DPNG_TESTS=0 && + make $@ + check_error + touch "$DIRNAME/obj/libpng.stamp" +fi + +# Openal +if [ ! -f "$DIRNAME/obj/openal.stamp" ]; then + echo "Compiling openal" + mkdir -p "$DIRNAME/obj/openal" + cp -a -f "$DIRNAME/../lib/openal/"* "$DIRNAME/obj/openal" + + cd "$DIRNAME/obj/openal" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST \ + -DALSOFT_UTILS=0 \ + -DALSOFT_EXAMPLES=0 \ + -DALSOFT_TESTS=0 \ + -DLIBTYPE=STATIC && + make $@ + check_error + touch "$DIRNAME/obj/openal.stamp" +fi + +# OpenSSL +if [ ! -f "$DIRNAME/obj/openssl.stamp" ]; then + echo "Compiling openssl" + mkdir -p "$DIRNAME/obj/openssl" + cp -a -f "$DIRNAME/../lib/openssl/"* "$DIRNAME/obj/openssl" + + cd "$DIRNAME/obj/openssl" + ./Configure android --cross-compile-prefix="$HOST-" + make $@ + check_error + touch "$DIRNAME/obj/openssl.stamp" +fi + +# Curl +if [ ! -f "$DIRNAME/obj/curl.stamp" ]; then + echo "Compiling curl" + mkdir -p "$DIRNAME/obj/curl" + cp -a -f "$DIRNAME/../lib/curl/"* "$DIRNAME/obj/curl" + + cd "$DIRNAME/obj/curl" + CPPFLAGS="-I$DIRNAME/obj/openssl/include $CPPFLAGS" \ + LDFLAGS="-L$DIRNAME/obj/openssl/ $LDFLAGS" \ + ./configure --host=$HOST \ + --with-ssl \ + --disable-shared \ + --enable-static \ + --enable-threaded-resolver && + make $@ + check_error + touch "$DIRNAME/obj/curl.stamp" +fi + +# Jpeglib +if [ ! -f "$DIRNAME/obj/jpeglib.stamp" ]; then + echo "Compiling jpeglib" + mkdir -p "$DIRNAME/obj/jpeglib" + cp -a -f "$DIRNAME/../lib/jpeglib/"* "$DIRNAME/obj/jpeglib" + + cd "$DIRNAME/obj/jpeglib" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/jpeglib.stamp" +fi + +# Libogg +if [ ! -f "$DIRNAME/obj/libogg.stamp" ]; then + echo "Compiling libogg" + mkdir -p "$DIRNAME/obj/libogg" + cp -a -f "$DIRNAME/../lib/libogg/"* "$DIRNAME/obj/libogg" + + cd "$DIRNAME/obj/libogg" + ./configure --host=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/libogg.stamp" +fi + +# Libvorbis +if [ ! -f "$DIRNAME/obj/libvorbis.stamp" ]; then + echo "Compiling libvorbis" + mkdir -p "$DIRNAME/obj/libvorbis" + cp -a -f "$DIRNAME/../lib/libvorbis/"* "$DIRNAME/obj/libvorbis" + + cd "$DIRNAME/obj/libvorbis" + CPPFLAGS="-I$DIRNAME/obj/libogg/include $CPPFLAGS" \ + LDFLAGS="-L$DIRNAME/obj/libogg/src/.libs $LDFLAGS" \ + ./configure --host=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/libvorbis.stamp" +fi + +# STK +echo "Compiling STK" +cd "$DIRNAME" +${NDK_PATH}/ndk-build $@ \ + APP_BUILD_SCRIPT="$NDK_BUILD_SCRIPT" \ + APP_ABI="$NDK_ABI" \ + APP_PLATFORM="$NDK_PLATFORM" \ + APP_CPPFLAGS="$NDK_CPPFLAGS" \ + APP_STL=gnustl_static + +check_error + +# Build apk +echo "Building APK" +ant debug -Dsdk.dir="$SDK_PATH" -Dtarget=$NDK_PLATFORM +check_error diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33de3e8038eefec0c3eb61f79892497c080e8025 GIT binary patch literal 5874 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 BTLAz7 literal 0 HcmV?d00001 diff --git a/android/res/drawable-mdpi/icon.png b/android/res/drawable-mdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0a8f484e6bf35f468b4eab2f8834c8d31f5d2779 GIT binary patch literal 3208 zcmV;340rR1P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 0 HcmV?d00001 diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7bbbc461b34df09cad5a35b2191714ae6cbe80ee GIT binary patch literal 17129 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 literal 0 HcmV?d00001 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a7b389fe791a47ffa21ee4a30b2335f9d2d0d9c0 GIT binary patch literal 37650 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 literal 0 HcmV?d00001 diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100644 index 000000000..931a3c2c9 --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,4 @@ + + + SuperTuxKart + diff --git a/cmake/Toolchain-android.cmake b/cmake/Toolchain-android.cmake new file mode 100644 index 000000000..0ab179eee --- /dev/null +++ b/cmake/Toolchain-android.cmake @@ -0,0 +1,32 @@ +# Cross-compiling requires CMake 2.6 or newer. Example: +# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt -DHOST=arm-linux-androideabi +# Where 'arm-linux-androideabi' is the host prefix for the cross-compiler. If +# you already have a toolchain file setup, you may use that instead of this +# file. Make sure to set CMAKE_FIND_ROOT_PATH to where the NDK toolchain was +# installed (e.g. "$ENV{HOME}/toolchains/arm-linux-androideabi-r10c-21"). + +# the name of the target operating system +SET(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +SET(CMAKE_C_COMPILER "${HOST}-gcc") +SET(CMAKE_CXX_COMPILER "${HOST}-g++") +SET(CMAKE_RC_COMPILER "${HOST}-windres") + +# here is the target environment located +SET(CMAKE_FIND_ROOT_PATH $ENV{NDK_TOOLCHAIN_PATH}) + +# here is where stuff gets installed to +SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# set env vars so that pkg-config will look in the appropriate directory for +# .pc files (as there seems to be no way to force using ${HOST}-pkg-config) +set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig") +set(ENV{PKG_CONFIG_PATH} "") diff --git a/lib/irrlicht/CMakeLists.txt b/lib/irrlicht/CMakeLists.txt index 03d2ec125..ac3e0d49c 100644 --- a/lib/irrlicht/CMakeLists.txt +++ b/lib/irrlicht/CMakeLists.txt @@ -112,6 +112,7 @@ source/Irrlicht/CImageLoaderPNG.cpp source/Irrlicht/CImageWriterBMP.cpp source/Irrlicht/CImageWriterJPG.cpp source/Irrlicht/CImageWriterPNG.cpp +source/Irrlicht/CIrrDeviceAndroid.cpp source/Irrlicht/CIrrDeviceConsole.cpp source/Irrlicht/CIrrDeviceFB.cpp source/Irrlicht/CIrrDeviceLinux.cpp @@ -243,6 +244,7 @@ source/Irrlicht/CImageLoaderPNG.h source/Irrlicht/CImageWriterBMP.h source/Irrlicht/CImageWriterJPG.h source/Irrlicht/CImageWriterPNG.h +source/Irrlicht/CIrrDeviceAndroid.h source/Irrlicht/CIrrDeviceConsole.h source/Irrlicht/CIrrDeviceFB.h source/Irrlicht/CIrrDeviceLinux.h diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp new file mode 100644 index 000000000..3e491ff4b --- /dev/null +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp @@ -0,0 +1,963 @@ +// Copyright (C) 2002-2007 Nikolaus Gebhardt +// Copyright (C) 2007-2011 Christian Stehno +// Copyright (C) 2016-2017 Dawid Gan +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "CIrrDeviceAndroid.h" + +#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_ + +#include +#include "os.h" +#include "CFileSystem.h" +#include "COGLES2Driver.h" + +namespace irr +{ + namespace video + { + IVideoDriver* createOGLES1Driver(const SIrrlichtCreationParameters& params, + video::SExposedVideoData& data, io::IFileSystem* io); + + IVideoDriver* createOGLES2Driver(const SIrrlichtCreationParameters& params, + video::SExposedVideoData& data, io::IFileSystem* io); + } +} + +namespace irr +{ + +// These variables must be global. Otherwise initialization will reach infinite +// loop after creating the device second time (i.e. the NULL driver and then +// GLES2 driver). We get initialization events from Android only once. +bool CIrrDeviceAndroid::IsPaused = true; +bool CIrrDeviceAndroid::IsFocused = false; +bool CIrrDeviceAndroid::IsStarted = false; +bool CIrrDeviceAndroid::IsClosing = false; + +//! constructor +CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters& param) + : CIrrDeviceStub(param), + Accelerometer(0), + Gyroscope(0), + IsMousePressed(false) +{ + #ifdef _DEBUG + setDebugName("CIrrDeviceAndroid"); + #endif + + Android = (android_app *)(param.PrivateData); + + IsClosing = Android->destroyRequested; + + Android->userData = this; + Android->onAppCmd = handleAndroidCommand; + Android->onInputEvent = handleInput; + + SensorManager = ASensorManager_getInstance(); + SensorEventQueue = ASensorManager_createEventQueue(SensorManager, + Android->looper, LOOPER_ID_USER, NULL, NULL); + + ANativeActivity_setWindowFlags(Android->activity, + AWINDOW_FLAG_KEEP_SCREEN_ON | + AWINDOW_FLAG_FULLSCREEN, 0); + + createKeyMap(); + + // Create cursor control + CursorControl = new CCursorControl(); + + os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG); + + while ((!IsStarted || !IsFocused || IsPaused) && !IsClosing) + { + s32 events = 0; + android_poll_source* source = 0; + + s32 id = ALooper_pollAll(-1, NULL, &events, (void**)&source); + + if (id >=0 && source != NULL) + { + source->process(Android, source); + } + } + + assert(Android->window); + os::Printer::log("Done", ELL_DEBUG); + + ExposedVideoData.OGLESAndroid.Window = Android->window; + + getVideoModeList(); + + createDriver(); + + if (VideoDriver) + createGUIAndScene(); +} + + +CIrrDeviceAndroid::~CIrrDeviceAndroid() +{ + Android->userData = NULL; +} + +video::IVideoModeList* CIrrDeviceAndroid::getVideoModeList() +{ + if (Android == NULL || Android->window == NULL) + return NULL; + + core::dimension2d size = core::dimension2d( + ANativeWindow_getWidth(Android->window), + ANativeWindow_getHeight(Android->window)); + + CreationParams.WindowSize.Width = size.Width; + CreationParams.WindowSize.Height = size.Height; + + if (!VideoModeList.getVideoModeCount()) + { + VideoModeList.addMode(size, 32); + VideoModeList.setDesktop(32, size); + } + + return &VideoModeList; +} + +void CIrrDeviceAndroid::createDriver() +{ + // Create the driver. + switch(CreationParams.DriverType) + { + case video::EDT_OGLES1: + #ifdef _IRR_COMPILE_WITH_OGLES1_ + VideoDriver = video::createOGLES1Driver(CreationParams, ExposedVideoData, FileSystem); + #else + os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR); + #endif + break; + + case video::EDT_OGLES2: + #ifdef _IRR_COMPILE_WITH_OGLES2_ + VideoDriver = video::createOGLES2Driver(CreationParams, ExposedVideoData, FileSystem); + #else + os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR); + #endif + break; + + case video::EDT_NULL: + VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize); + break; + + default: + os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR); + break; + } +} + +bool CIrrDeviceAndroid::run() +{ + os::Timer::tick(); + + while (!IsClosing) + { + s32 Events = 0; + android_poll_source* Source = 0; + bool should_run = (IsStarted && IsFocused && !IsPaused) || IsClosing; + s32 id = ALooper_pollAll(should_run ? 0 : -1, NULL, &Events, + (void**)&Source); + + if (id < 0) + break; + + if (Source) + { + Source->process(Android, Source); + } + + // if a sensor has data, we'll process it now. + if (id == LOOPER_ID_USER) + { + ASensorEvent event; + while (ASensorEventQueue_getEvents(SensorEventQueue, &event, 1) > 0) + { + switch (event.type) + { + case ASENSOR_TYPE_ACCELEROMETER: + SEvent accEvent; + accEvent.EventType = EET_ACCELEROMETER_EVENT; + accEvent.AccelerometerEvent.X = event.acceleration.x; + accEvent.AccelerometerEvent.Y = event.acceleration.y; + accEvent.AccelerometerEvent.Z = event.acceleration.z; + + postEventFromUser(accEvent); + break; + + case ASENSOR_TYPE_GYROSCOPE: + SEvent gyroEvent; + gyroEvent.EventType = EET_GYROSCOPE_EVENT; + gyroEvent.GyroscopeEvent.X = event.vector.x; + gyroEvent.GyroscopeEvent.Y = event.vector.y; + gyroEvent.GyroscopeEvent.Z = event.vector.z; + + postEventFromUser(gyroEvent); + break; + default: + break; + } + } + } + } + + return !IsClosing; +} + +void CIrrDeviceAndroid::yield() +{ + struct timespec ts = {0,1}; + nanosleep(&ts, NULL); +} + +void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer) +{ + const bool wasStopped = Timer ? Timer->isStopped() : true; + + struct timespec ts; + ts.tv_sec = (time_t) (timeMs / 1000); + ts.tv_nsec = (long) (timeMs % 1000) * 1000000; + + if (pauseTimer && !wasStopped) + Timer->stop(); + + nanosleep(&ts, NULL); + + if (pauseTimer && !wasStopped) + Timer->start(); +} + +void CIrrDeviceAndroid::setWindowCaption(const wchar_t* text) +{ +} + +bool CIrrDeviceAndroid::present(video::IImage* surface, void* windowId, + core::rect* srcClip) +{ + return true; +} + +bool CIrrDeviceAndroid::isWindowActive() const +{ + return (IsFocused && !IsPaused); +} + +bool CIrrDeviceAndroid::isWindowFocused() const +{ + return IsFocused; +} + +bool CIrrDeviceAndroid::isWindowMinimized() const +{ + return IsPaused; +} + +void CIrrDeviceAndroid::closeDevice() +{ +} + +void CIrrDeviceAndroid::setResizable(bool resize) +{ +} + +void CIrrDeviceAndroid::minimizeWindow() +{ +} + +void CIrrDeviceAndroid::maximizeWindow() +{ +} + +void CIrrDeviceAndroid::restoreWindow() +{ +} + +E_DEVICE_TYPE CIrrDeviceAndroid::getType() const +{ + return EIDT_ANDROID; +} + +void CIrrDeviceAndroid::handleAndroidCommand(android_app* app, int32_t cmd) +{ + CIrrDeviceAndroid* device = (CIrrDeviceAndroid *)app->userData; + + switch (cmd) + { + case APP_CMD_SAVE_STATE: + os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG); + break; + case APP_CMD_INIT_WINDOW: + os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG); + + if (device != NULL) + { + device->getExposedVideoData().OGLESAndroid.Window = app->window; + + // If the Android app is resumed, we need to re-create EGL surface + // to allow to draw something on it again. + if (device->VideoDriver != NULL && + device->CreationParams.DriverType == video::EDT_OGLES2) + { + video::COGLES2Driver* driver = (video::COGLES2Driver*)(device->VideoDriver); + driver->reloadEGLSurface(app->window); + } + } + + IsStarted = true; + break; + case APP_CMD_TERM_WINDOW: + os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG); + IsStarted = false; + break; + case APP_CMD_GAINED_FOCUS: + os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG); + IsFocused = true; + break; + case APP_CMD_LOST_FOCUS: + os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG); + IsFocused = false; + break; + case APP_CMD_DESTROY: + os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG); + IsClosing = true; + // Make sure that state variables are set to the default state + // when the app is destroyed + IsPaused = true; + IsFocused = false; + IsStarted = false; + break; + case APP_CMD_PAUSE: + os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG); + IsPaused = true; + break; + case APP_CMD_RESUME: + os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG); + IsPaused = false; + break; + case APP_CMD_START: + os::Printer::log("Android command APP_CMD_START", ELL_DEBUG); + break; + case APP_CMD_STOP: + os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG); + break; + case APP_CMD_WINDOW_RESIZED: + os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG); + break; + case APP_CMD_CONFIG_CHANGED: + os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG); + break; + case APP_CMD_LOW_MEMORY: + os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG); + break; + default: + break; + } + + if (device != NULL) + { + SEvent event; + event.EventType = EET_SYSTEM_EVENT; + event.SystemEvent.EventType = ESET_ANDROID_CMD; + event.SystemEvent.AndroidCmd.Cmd = cmd; + device->postEventFromUser(event); + } +} + +s32 CIrrDeviceAndroid::handleInput(android_app* app, AInputEvent* androidEvent) +{ + CIrrDeviceAndroid* device = (CIrrDeviceAndroid*)app->userData; + s32 status = 0; + + if (device == NULL) + return status; + + + switch (AInputEvent_getType(androidEvent)) + { + case AINPUT_EVENT_TYPE_MOTION: + { + SEvent event; + event.EventType = EET_TOUCH_INPUT_EVENT; + + s32 eventAction = AMotionEvent_getAction(androidEvent); + +#if 0 + // Useful for debugging. We might have to pass some of those infos on at some point. + // but preferably device independent (so iphone can use same irrlicht flags). + int32_t flags = AMotionEvent_getFlags(androidEvent); + os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG); + int32_t metaState = AMotionEvent_getMetaState(androidEvent); + os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG); + int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent); + os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG); +#endif + + bool touchReceived = true; + bool simulate_mouse = false; + core::position2d mouse_pos = core::position2d(0, 0); + + switch (eventAction & AMOTION_EVENT_ACTION_MASK) + { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + event.TouchInput.Event = ETIE_PRESSED_DOWN; + break; + case AMOTION_EVENT_ACTION_MOVE: + event.TouchInput.Event = ETIE_MOVED; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_CANCEL: + event.TouchInput.Event = ETIE_LEFT_UP; + break; + default: + touchReceived = false; + break; + } + + if (touchReceived) + { + s32 count = 1; + s32 idx = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + + // Process all touches for move action. + if (event.TouchInput.Event == ETIE_MOVED) + { + count = AMotionEvent_getPointerCount(androidEvent); + idx = 0; + } + + for (s32 i = 0; i < count; ++i) + { + event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i + idx); + event.TouchInput.X = AMotionEvent_getX(androidEvent, i + idx); + event.TouchInput.Y = AMotionEvent_getY(androidEvent, i + idx); + + device->postEventFromUser(event); + + if (event.TouchInput.ID == 0) + { + simulate_mouse = true; + mouse_pos.X = event.TouchInput.X; + mouse_pos.Y = event.TouchInput.Y; + } + } + + status = 1; + } + + // Simulate mouse event for first finger on multitouch device. + // This allows to click on GUI elements. + if (simulate_mouse) + { + device->CursorControl->setPosition(mouse_pos); + + SEvent irrevent; + bool send_event = true; + + switch (event.TouchInput.Event) + { + case ETIE_PRESSED_DOWN: + irrevent.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + device->IsMousePressed = true; + break; + case ETIE_LEFT_UP: + irrevent.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + device->IsMousePressed = false; + break; + case ETIE_MOVED: + irrevent.MouseInput.Event = EMIE_MOUSE_MOVED; + break; + default: + send_event = false; + break; + } + + if (send_event) + { + irrevent.MouseInput.Control = false; + irrevent.MouseInput.Shift = false; + irrevent.MouseInput.ButtonStates = device->IsMousePressed ? + irr::EMBSM_LEFT : 0; + irrevent.EventType = EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.X = mouse_pos.X; + irrevent.MouseInput.Y = mouse_pos.Y; + + device->postEventFromUser(irrevent); + } + } + + break; + } + case AINPUT_EVENT_TYPE_KEY: + { + bool ignore_event = false; + + SEvent event; + event.EventType = EET_KEY_INPUT_EVENT; + event.KeyInput.Char = 0; + event.KeyInput.PressedDown = false; + event.KeyInput.Key = KEY_UNKNOWN; + + int32_t keyCode = AKeyEvent_getKeyCode(androidEvent); + int32_t keyAction = AKeyEvent_getAction(androidEvent); + int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent); + int32_t keyRepeat = AKeyEvent_getRepeatCount(androidEvent); + + if (keyAction == AKEY_EVENT_ACTION_DOWN) + { + event.KeyInput.PressedDown = true; + } + else if (keyAction == AKEY_EVENT_ACTION_UP) + { + event.KeyInput.PressedDown = false; + } + else if (keyAction == AKEY_EVENT_ACTION_MULTIPLE) + { + // TODO: Multiple duplicate key events have occurred in a row, + // or a complex string is being delivered. The repeat_count + // property of the key event contains the number of times the + // given key code should be executed. + // I guess this might necessary for more complicated i18n key input, + // but don't see yet how to handle this correctly. + } + + event.KeyInput.Shift = (keyMetaState & AMETA_SHIFT_ON || + keyMetaState & AMETA_SHIFT_LEFT_ON || + keyMetaState & AMETA_SHIFT_RIGHT_ON); + + event.KeyInput.Control = (keyMetaState & AMETA_CTRL_ON || + keyMetaState & AMETA_CTRL_LEFT_ON || + keyMetaState & AMETA_CTRL_RIGHT_ON); + + event.KeyInput.SystemKeyCode = (u32)keyCode; + + if (keyCode >= 0 && (u32)keyCode < device->KeyMap.size()) + { + event.KeyInput.Key = device->KeyMap[keyCode]; + } + + if (event.KeyInput.Key > 0) + { + device->getKeyChar(event); + } + + // Handle an event when back button in pressed just like an escape key + // and also avoid repeating the event to avoid some strange behaviour + if (event.KeyInput.SystemKeyCode == AKEYCODE_BACK) + { + status = 1; + + if (event.KeyInput.PressedDown == false || keyRepeat > 0) + { + ignore_event = true; + } + } + + // Mark escape key event as handled by application to avoid receiving + // AKEYCODE_BACK key event + if (event.KeyInput.SystemKeyCode == AKEYCODE_ESCAPE) + { + status = 1; + } + + if (!ignore_event) + { + device->postEventFromUser(event); + } + + break; + } + default: + break; + } + + return status; +} + +video::SExposedVideoData& CIrrDeviceAndroid::getExposedVideoData() +{ + return ExposedVideoData; +} + +void CIrrDeviceAndroid::createKeyMap() +{ + KeyMap.set_used(223); + + KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN + KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT + KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT + KeyMap[3] = KEY_HOME; // AKEYCODE_HOME + KeyMap[4] = KEY_ESCAPE; // AKEYCODE_BACK + KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL + KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL + KeyMap[7] = KEY_KEY_0; // AKEYCODE_0 + KeyMap[8] = KEY_KEY_1; // AKEYCODE_1 + KeyMap[9] = KEY_KEY_2; // AKEYCODE_2 + KeyMap[10] = KEY_KEY_3; // AKEYCODE_3 + KeyMap[11] = KEY_KEY_4; // AKEYCODE_4 + KeyMap[12] = KEY_KEY_5; // AKEYCODE_5 + KeyMap[13] = KEY_KEY_6; // AKEYCODE_6 + KeyMap[14] = KEY_KEY_7; // AKEYCODE_7 + KeyMap[15] = KEY_KEY_8; // AKEYCODE_8 + KeyMap[16] = KEY_KEY_9; // AKEYCODE_9 + KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR + KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND + KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP + KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN + KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT + KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT + KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER + KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP + KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN + KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER + KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA + KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR + KeyMap[29] = KEY_KEY_A; // AKEYCODE_A + KeyMap[30] = KEY_KEY_B; // AKEYCODE_B + KeyMap[31] = KEY_KEY_C; // AKEYCODE_C + KeyMap[32] = KEY_KEY_D; // AKEYCODE_D + KeyMap[33] = KEY_KEY_E; // AKEYCODE_E + KeyMap[34] = KEY_KEY_F; // AKEYCODE_F + KeyMap[35] = KEY_KEY_G; // AKEYCODE_G + KeyMap[36] = KEY_KEY_H; // AKEYCODE_H + KeyMap[37] = KEY_KEY_I; // AKEYCODE_I + KeyMap[38] = KEY_KEY_J; // AKEYCODE_J + KeyMap[39] = KEY_KEY_K; // AKEYCODE_K + KeyMap[40] = KEY_KEY_L; // AKEYCODE_L + KeyMap[41] = KEY_KEY_M; // AKEYCODE_M + KeyMap[42] = KEY_KEY_N; // AKEYCODE_N + KeyMap[43] = KEY_KEY_O; // AKEYCODE_O + KeyMap[44] = KEY_KEY_P; // AKEYCODE_P + KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q + KeyMap[46] = KEY_KEY_R; // AKEYCODE_R + KeyMap[47] = KEY_KEY_S; // AKEYCODE_S + KeyMap[48] = KEY_KEY_T; // AKEYCODE_T + KeyMap[49] = KEY_KEY_U; // AKEYCODE_U + KeyMap[50] = KEY_KEY_V; // AKEYCODE_V + KeyMap[51] = KEY_KEY_W; // AKEYCODE_W + KeyMap[52] = KEY_KEY_X; // AKEYCODE_X + KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y + KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z + KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA + KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD + KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT + KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT + KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT + KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT + KeyMap[61] = KEY_TAB; // AKEYCODE_TAB + KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE + KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM + KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER + KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE + KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER + KeyMap[67] = KEY_BACK; // AKEYCODE_DEL + KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE + KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS + KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS + KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET + KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET + KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH + KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON + KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE + KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH + KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT + KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM + KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK + KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus) + KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS + KeyMap[82] = KEY_MENU; // AKEYCODE_MENU + KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION + KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH + KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE + KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP + KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT + KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS + KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND + KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD + KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE + KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP + KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN + KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS + KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET + + // following look like controller inputs + KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A + KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B + KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C + KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X + KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y + KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z + KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1 + KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1 + KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2 + KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2 + KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL + KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR + KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START + KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT + KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE + + KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE + KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL + KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT + KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT + KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK + KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK + KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT + KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT + KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION + KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ + KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK + KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME + KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END + KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT + KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD + KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY + KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE + KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE + KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT + KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD + KeyMap[131] = KEY_F1; // AKEYCODE_F1 + KeyMap[132] = KEY_F2; // AKEYCODE_F2 + KeyMap[133] = KEY_F3; // AKEYCODE_F3 + KeyMap[134] = KEY_F4; // AKEYCODE_F4 + KeyMap[135] = KEY_F5; // AKEYCODE_F5 + KeyMap[136] = KEY_F6; // AKEYCODE_F6 + KeyMap[137] = KEY_F7; // AKEYCODE_F7 + KeyMap[138] = KEY_F8; // AKEYCODE_F8 + KeyMap[139] = KEY_F9; // AKEYCODE_F9 + KeyMap[140] = KEY_F10; // AKEYCODE_F10 + KeyMap[141] = KEY_F11; // AKEYCODE_F11 + KeyMap[142] = KEY_F12; // AKEYCODE_F12 + KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK + KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0 + KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1 + KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2 + KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3 + KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4 + KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5 + KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6 + KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7 + KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8 + KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9 + KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE + KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY + KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT + KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD + KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT + KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA + KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER + KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS + KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN + KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN + KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE + KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO + KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP + KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN + KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN + KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT + KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV + KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW + KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE + KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR + KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK + KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS + KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS + KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER + KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT + KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER + KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT + KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER + KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT + KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED + KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN + KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW + KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE + KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH + KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1 + KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2 + KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3 + KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4 + KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5 + KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6 + KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7 + KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8 + KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9 + KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10 + KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11 + KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12 + KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13 + KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14 + KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15 + KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16 + KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH + KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE + KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE + KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS + KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR + KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC + KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR + KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU + KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU + KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN + KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN + KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA + KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN + KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO + KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA + KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST + KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN + KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP , + KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK +} + +void CIrrDeviceAndroid::getKeyChar(SEvent& event) +{ + // Handle ASCII chars + + event.KeyInput.Char = 0; + + // A-Z + if (event.KeyInput.SystemKeyCode > 28 && event.KeyInput.SystemKeyCode < 55) + { + if (event.KeyInput.Shift) + { + event.KeyInput.Char = event.KeyInput.SystemKeyCode + 36; + } + else + { + event.KeyInput.Char = event.KeyInput.SystemKeyCode + 68; + } + } + + // 0-9 + else if (event.KeyInput.SystemKeyCode > 6 && event.KeyInput.SystemKeyCode < 17) + { + event.KeyInput.Char = event.KeyInput.SystemKeyCode + 41; + } + + else if (event.KeyInput.SystemKeyCode == AKEYCODE_BACK) + { + event.KeyInput.Char = 8; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_DEL) + { + event.KeyInput.Char = 8; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_TAB) + { + event.KeyInput.Char = 9; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_ENTER) + { + event.KeyInput.Char = 13; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_SPACE) + { + event.KeyInput.Char = 32; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_COMMA) + { + event.KeyInput.Char = 44; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_PERIOD) + { + event.KeyInput.Char = 46; + } +} + +bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval) +{ + if (!isAccelerometerAvailable()) + return false; + + ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer); + ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer, + (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds + + os::Printer::log("Activated accelerometer", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::deactivateAccelerometer() +{ + if (!Accelerometer) + return false; + + ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer); + Accelerometer = 0; + os::Printer::log("Deactivated accelerometer", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::isAccelerometerActive() +{ + return (Accelerometer != NULL); +} + +bool CIrrDeviceAndroid::isAccelerometerAvailable() +{ + if (!Accelerometer) + { + Accelerometer = ASensorManager_getDefaultSensor(SensorManager, + ASENSOR_TYPE_ACCELEROMETER); + } + + return (Accelerometer != NULL); +} + +bool CIrrDeviceAndroid::activateGyroscope(float updateInterval) +{ + if (!isGyroscopeAvailable()) + return false; + + ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope); + ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope, + (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds + + os::Printer::log("Activated gyroscope", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::deactivateGyroscope() +{ + if (!Gyroscope) + return false; + + ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope); + Gyroscope = 0; + os::Printer::log("Deactivated gyroscope", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::isGyroscopeActive() +{ + return (Gyroscope != NULL); +} + +bool CIrrDeviceAndroid::isGyroscopeAvailable() +{ + if (!Gyroscope) + { + Gyroscope = ASensorManager_getDefaultSensor(SensorManager, + ASENSOR_TYPE_GYROSCOPE); + } + + return (Gyroscope != NULL); +} + + +} // end namespace irr + +#endif + + diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h new file mode 100644 index 000000000..86d3ce1fe --- /dev/null +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h @@ -0,0 +1,126 @@ +// Copyright (C) 2002-2011 Nikolaus Gebhardt +// Copyright (C) 2016-2017 Dawid Gan +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_IRR_DEVICE_ANDROID_H_INCLUDED__ +#define __C_IRR_DEVICE_ANDROID_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_ + +#include +#include +#include +#include "CIrrDeviceStub.h" +#include "IrrlichtDevice.h" +#include "IImagePresenter.h" +#include "ICursorControl.h" + + +namespace irr +{ + + class CIrrDeviceAndroid : public CIrrDeviceStub, video::IImagePresenter + { + public: + //! constructor + CIrrDeviceAndroid(const SIrrlichtCreationParameters& param); + + //! destructor + virtual ~CIrrDeviceAndroid(); + + virtual bool run(); + virtual void yield(); + virtual void sleep(u32 timeMs, bool pauseTimer=false); + virtual void setWindowCaption(const wchar_t* text); + virtual bool present(video::IImage* surface, void* windowId, core::rect* srcClip); + virtual bool isWindowActive() const; + virtual bool isWindowFocused() const; + virtual bool isWindowMinimized() const; + virtual void closeDevice(); + virtual void setResizable( bool resize=false ); + virtual void minimizeWindow(); + virtual void maximizeWindow(); + virtual void restoreWindow(); + virtual E_DEVICE_TYPE getType() const; + virtual bool activateAccelerometer(float updateInterval); + virtual bool deactivateAccelerometer(); + virtual bool isAccelerometerActive(); + virtual bool isAccelerometerAvailable(); + virtual bool activateGyroscope(float updateInterval); + virtual bool deactivateGyroscope(); + virtual bool isGyroscopeActive(); + virtual bool isGyroscopeAvailable(); + video::IVideoModeList* getVideoModeList(); + + class CCursorControl : public gui::ICursorControl + { + public: + + CCursorControl() : CursorPos(core::position2d(0, 0)) {} + virtual void setVisible(bool visible) {} + virtual bool isVisible() const {return false;} + virtual void setPosition(const core::position2d &pos) + { + setPosition(pos.X, pos.Y); + } + virtual void setPosition(f32 x, f32 y) + { + CursorPos.X = x; + CursorPos.Y = y; + } + virtual void setPosition(const core::position2d &pos) + { + setPosition(pos.X, pos.Y); + } + virtual void setPosition(s32 x, s32 y) + { + CursorPos.X = x; + CursorPos.Y = y; + } + virtual const core::position2d& getPosition() + { + return CursorPos; + } + virtual core::position2d getRelativePosition() + { + return core::position2d(0, 0); + } + virtual void setReferenceRect(core::rect* rect=0) {} + private: + core::position2d CursorPos; + }; + + private: + android_app* Android; + ASensorManager* SensorManager; + ASensorEventQueue* SensorEventQueue; + const ASensor* Accelerometer; + const ASensor* Gyroscope; + + static bool IsPaused; + static bool IsFocused; + static bool IsStarted; + static bool IsClosing; + + bool IsMousePressed; + + video::SExposedVideoData ExposedVideoData; + + core::array KeyMap; + + void createDriver(); + void createKeyMap(); + void getKeyChar(SEvent& event); + video::SExposedVideoData& getExposedVideoData(); + + static void handleAndroidCommand(android_app* app, int32_t cmd); + static s32 handleInput(android_app* app, AInputEvent* event); + }; + +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_ANDROID_DEVICE_ +#endif // __C_IRR_DEVICE_ANDROID_H_INCLUDED__ diff --git a/lib/libpng/CMakeLists.txt b/lib/libpng/CMakeLists.txt index b8d476b12..68aa98cac 100644 --- a/lib/libpng/CMakeLists.txt +++ b/lib/libpng/CMakeLists.txt @@ -60,6 +60,7 @@ if(NOT WIN32) if(NOT M_LIBRARY) message(STATUS "math library 'libm' not found - floating point support disabled") + set(M_LIBRARY "") endif() else() # not needed on windows diff --git a/sources.cmake b/sources.cmake index d4f28ae4d..ddc029d4f 100644 --- a/sources.cmake +++ b/sources.cmake @@ -1,4 +1,4 @@ -# Modify this file to change the last-modified date when you add/remove a file. +# Modify this file to change the last-modified date when you add/remove a file. # This will then trigger a new cmake run automatically. file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp") file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp") diff --git a/src/io/assets_android.cpp b/src/io/assets_android.cpp new file mode 100644 index 000000000..11f6f70fd --- /dev/null +++ b/src/io/assets_android.cpp @@ -0,0 +1,433 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "graphics/irr_driver.hpp" +#include "io/assets_android.hpp" +#include "io/file_manager.hpp" +#include "utils/log.hpp" +#include "utils/progress_bar_android.hpp" + +#include +#include +#include + +#ifdef ANDROID +#include +#endif + +//----------------------------------------------------------------------------- +/** Assets Android constructor + */ +AssetsAndroid::AssetsAndroid(FileManager* file_manager) +{ + m_file_manager = file_manager; +} + +//----------------------------------------------------------------------------- +/** A function that detects a path where data directory is placed and that + * sets some environment variables that are used for finding data and + * home directory in a common code. + */ +void AssetsAndroid::init() +{ +#ifdef ANDROID + if (m_file_manager == NULL) + return; + + bool needs_extract_data = false; + const std::string version = std::string("supertuxkart.") + STK_VERSION; + + // Add some paths to check + std::vector paths; + + if (getenv("SUPERTUXKART_DATADIR")) + paths.push_back(getenv("SUPERTUXKART_DATADIR")); + + if (getenv("EXTERNAL_STORAGE")) + paths.push_back(getenv("EXTERNAL_STORAGE")); + + if (getenv("SECONDARY_STORAGE")) + paths.push_back(getenv("SECONDARY_STORAGE")); + + if (global_android_app->activity->externalDataPath) + paths.push_back(global_android_app->activity->externalDataPath); + + if (global_android_app->activity->internalDataPath) + paths.push_back(global_android_app->activity->internalDataPath); + + paths.push_back("/sdcard/"); + paths.push_back("/storage/sdcard0/"); + paths.push_back("/storage/sdcard1/"); + paths.push_back("/data/data/org.supertuxkart.stk/files/"); + + // Check if STK data is available somewhere + for (std::string path : paths) + { + Log::info("AssetsAndroid", "Check data files in: %s", path.c_str()); + + if (m_file_manager->fileExists(path + "/stk/data/" + version)) + { + Log::info("AssetsAndroid", "Data files found in: %s", path.c_str()); + m_stk_dir = path + "/stk"; + break; + } + + if (m_file_manager->fileExists(path + "/supertuxkart/data/" + version)) + { + Log::info("AssetsAndroid", "Data files found in: %s", path.c_str()); + m_stk_dir = path + "/supertuxkart"; + break; + } + } + + // Create data dir if it's not available anywhere + if (m_stk_dir.size() == 0) + { + for (std::string path : paths) + { + if (m_file_manager->checkAndCreateDirectoryP(path + "/stk/data")) + { + Log::info("AssetsAndroid", "Data directory created in: %s", + path.c_str()); + m_stk_dir = path + "/stk"; + needs_extract_data = true; + break; + } + } + } + + // We can't continue if STK dir has not been found + if (m_stk_dir.size() == 0) + { + Log::fatal("AssetsAndroid", "Fatal error: Couldn't find Supertuxkart " + "data directory"); + } + + // Check if assets were extracted properly + if (!m_file_manager->fileExists(m_stk_dir + "/.extracted") && + !needs_extract_data) + { + needs_extract_data = true; + Log::warn("AssetsAndroid", "Assets seem to be not extracted properly, " + "because the .extracted file doesn't exist. Force " + "extracting assets..."); + } + + if (!m_file_manager->checkAndCreateDirectoryP(m_stk_dir + "/home")) + { + Log::warn("AssetsAndroid", "Couldn't create home directory"); + } + + // Set some useful variables + setenv("SUPERTUXKART_DATADIR", m_stk_dir.c_str(), 1); + setenv("HOME", (m_stk_dir + "/home").c_str(), 1); + setenv("XDG_CONFIG_HOME", (m_stk_dir + "/home").c_str(), 1); + + // Extract data directory from apk if it's needed + if (needs_extract_data) + { + removeData(); + extractData(); + + if (!m_file_manager->fileExists(m_stk_dir + "/.extracted")) + { + Log::fatal("AssetsAndroid", "Fatal error: Assets were not " + "extracted properly"); + } + } + +#endif +} + +//----------------------------------------------------------------------------- +/** A function that extracts whole data directory from apk file to a real + * path in the filesystem + */ +void AssetsAndroid::extractData() +{ +#ifdef ANDROID + const std::string dirs_list = "directories.txt"; + + bool success = true; + + // Create .nomedia file + touchFile(m_stk_dir + "/.nomedia"); + + // Extract base directory first, so that we will be able to open the file + // with dir names + success = extractDir(""); + + if (!success) + { + Log::error("AssetsAndroid", "Error: Couldn't extract main directory."); + return; + } + + std::fstream file(m_stk_dir + "/" + dirs_list, std::ios::in); + + if (file.good()) + { + unsigned int lines_count = 0; + + while (!file.eof()) + { + std::string dir_name; + getline(file, dir_name); + + if (dir_name.length() == 0 || dir_name.at(0) == '#') + continue; + + lines_count++; + } + + if (lines_count > 0) + { + file.clear(); + file.seekg(0, std::ios::beg); + + ProgressBarAndroid* progress_bar = new ProgressBarAndroid(); + progress_bar->draw(0.0f); + unsigned int current_line = 1; + + while (!file.eof()) + { + std::string dir_name; + getline(file, dir_name); + + if (dir_name.length() == 0 || dir_name.at(0) == '#') + continue; + + success = extractDir(dir_name); + + assert(lines_count > 0); + progress_bar->draw((float)(current_line) / lines_count); + current_line++; + + if (progress_bar->closeEventReceived()) + { + success = false; + } + + if (!success) + break; + } + + delete progress_bar; + } + } + else + { + Log::warn("AssetsAndroid", "Warning: Cannot open %s file. Ignoring " + "extraction of other directories.", dirs_list.c_str()); + } + + file.close(); + + // Mark the extraction as successful if everything is ok + if (success) + { + touchFile(m_stk_dir + "/.extracted"); + } +#endif +} + +//----------------------------------------------------------------------------- +/** A function that extracts selected directory from apk file + * \param dir_name Directory to extract from assets + * \return True if successfully extracted + */ +bool AssetsAndroid::extractDir(std::string dir_name) +{ +#ifdef ANDROID + AAssetManager* amgr = global_android_app->activity->assetManager; + + Log::info("AssetsAndroid", "Extracting %s directory", + dir_name.length() > 0 ? dir_name.c_str() : "main"); + + std::string output_dir = dir_name; + + if (m_stk_dir.length() > 0) + { + output_dir = m_stk_dir + "/" + dir_name; + } + + AAssetDir* asset_dir = AAssetManager_openDir(amgr, dir_name.c_str()); + + if (asset_dir == NULL) + { + Log::warn("AssetsAndroid", "Couldn't get asset dir: %s", + dir_name.c_str()); + return true; + } + + if (!m_file_manager->checkAndCreateDirectoryP(output_dir)) + { + Log::warn("AssetsAndroid", "Couldn't create %s directory", + output_dir.c_str()); + return false; + } + + const int buf_size = 65536; + char* buf = new char[buf_size](); + bool extraction_failed = false; + + while (!extraction_failed) + { + const char* filename = AAssetDir_getNextFileName(asset_dir); + + // Check if finished + if (filename == NULL) + break; + + if (strlen(filename) == 0) + continue; + + std::string file_path = std::string(filename); + + if (dir_name.length() > 0) + { + file_path = dir_name + "/" + std::string(filename); + } + + AAsset* asset = AAssetManager_open(amgr, file_path.c_str(), + AASSET_MODE_STREAMING); + + if (asset == NULL) + { + Log::warn("AssetsAndroid", "Asset is null: %s", filename); + continue; + } + + std::string output_path = output_dir + "/" + std::string(filename); + std::fstream out_file(output_path, std::ios::out | std::ios::binary); + + if (!out_file.good()) + { + extraction_failed = true; + Log::error("AssetsAndroid", "Couldn't create a file: %s", filename); + AAsset_close(asset); + break; + } + + int nb_read = 0; + while ((nb_read = AAsset_read(asset, buf, buf_size)) > 0) + { + out_file.write(buf, nb_read); + + if (out_file.fail()) + { + extraction_failed = true; + break; + } + } + + out_file.close(); + + if (out_file.fail() || extraction_failed) + { + extraction_failed = true; + Log::error("AssetsAndroid", "Extraction failed for file: %s", + filename); + } + + AAsset_close(asset); + } + + delete[] buf; + + AAssetDir_close(asset_dir); + + return !extraction_failed; +#endif + + return false; +} + +//----------------------------------------------------------------------------- +/** A function that removes whole STK data directory + */ +void AssetsAndroid::removeData() +{ +#ifdef ANDROID + if (m_stk_dir.length() == 0) + return; + + // Make sure that we are not accidentally removing wrong directory + if (m_stk_dir.find("/stk") == std::string::npos && + m_stk_dir.find("/supertuxkart") == std::string::npos) + { + Log::error("AssetsAndroid", "Invalid data directory: %s", + m_stk_dir.c_str()); + assert(false); + return; + } + + std::set files; + m_file_manager->listFiles(files, m_stk_dir, true); + + for (std::string file : files) + { + if (file == m_stk_dir + "/." || file == m_stk_dir + "/..") + continue; + + // Don't delete home directory that contains configuration files + // and add-ons + if (file == m_stk_dir + "/home") + continue; + + // Don't delete .nomedia file. It has a sense to keep it for home + // directory, i.e. for textures of add-on karts etc. + if (file == m_stk_dir + "/.nomedia") + continue; + + Log::info("AssetsAndroid", "Deleting file: %s\n", file.c_str()); + + if (m_file_manager->isDirectory(file)) + { + m_file_manager->removeDirectory(file); + } + else + { + m_file_manager->removeFile(file); + } + } +#endif +} + +//----------------------------------------------------------------------------- +/** A function that creates empty file + * \param path A path to the file that should be created + */ +void AssetsAndroid::touchFile(std::string path) +{ +#ifdef ANDROID + if (m_file_manager->fileExists(path)) + return; + + std::fstream file(path, std::ios::out | std::ios::binary); + + if (!file.good()) + { + Log::warn("AssetsAndroid", "Error: Cannot create %s file.", + path.c_str()); + } + + file.close(); +#endif +} + +//----------------------------------------------------------------------------- diff --git a/src/io/assets_android.hpp b/src/io/assets_android.hpp new file mode 100644 index 000000000..0cccf566a --- /dev/null +++ b/src/io/assets_android.hpp @@ -0,0 +1,44 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_ASSETS_ANDROID_HPP +#define HEADER_ASSETS_ANDROID_HPP + +#include + +class FileManager; + +class AssetsAndroid +{ +private: + FileManager* m_file_manager; + std::string m_stk_dir; + + void extractData(); + bool extractDir(std::string dir_name); + void removeData(); + void touchFile(std::string path); + +public: + AssetsAndroid(FileManager* file_manager); + ~AssetsAndroid() {}; + + void init(); +}; + + +#endif diff --git a/src/main_android.cpp b/src/main_android.cpp new file mode 100644 index 000000000..ec3505584 --- /dev/null +++ b/src/main_android.cpp @@ -0,0 +1,62 @@ +#ifdef ANDROID + +#include "config/user_config.hpp" +#include "graphics/irr_driver.hpp" +#include "utils/log.hpp" + + +extern int main(int argc, char *argv[]); + +void override_default_params() +{ + // It has an effect only on the first run, when config file is created. + // So that we can still modify these params in STK options and user's + // choice will be then remembered. + + // Set smaller texture size to avoid high RAM usage + UserConfigParams::m_max_texture_size = 256; + UserConfigParams::m_high_definition_textures = false; + + // Disable advanced lighting by default to make the game playable + UserConfigParams::m_dynamic_lights = false; + + // Enable touch steering and screen keyboard + UserConfigParams::m_multitouch_enabled = true; + UserConfigParams::m_screen_keyboard = true; + + // It shouldn't matter, but STK is always run in fullscreen on android + UserConfigParams::m_fullscreen = true; + + // Make sure that user can play every track even if there are installed + // only few tracks and it's impossible to finish overworld challenges + UserConfigParams::m_everything_unlocked = true; + + // Create default user istead of showing login screen to make life easier + UserConfigParams::m_enforce_current_player = true; + + // Just for debugging + UserConfigParams::m_log_errors_to_console = true; +} + +void android_main(struct android_app* app) +{ + Log::info("AndroidMain", "Loading application..."); + + app_dummy(); + + override_default_params(); + + global_android_app = app; + main(0, {}); + + Log::info("AndroidMain", "Closing STK..."); + + // TODO: Irrlicht device is properly waiting for destroy event, but + // some global variables are not initialized/cleared in functions and thus + // its state is remembered when the window is restored. We will use exit + // call to make sure that all variables are cleared until a proper fix will + // be done. + exit(0); +} + +#endif diff --git a/src/utils/progress_bar_android.cpp b/src/utils/progress_bar_android.cpp new file mode 100644 index 000000000..1636c6e37 --- /dev/null +++ b/src/utils/progress_bar_android.cpp @@ -0,0 +1,205 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#include "config/user_config.hpp" +#include "graphics/irr_driver.hpp" +#include "utils/log.hpp" +#include "utils/progress_bar_android.hpp" + +#ifdef ANDROID + +ProgressBarAndroid::ProgressBarAndroid() +{ + m_program = 0; + m_vertex_shader = 0; + m_fragment_shader = 0; + m_position = 0; + m_progress = 0; + m_vbo = 0; + m_device = NULL; + m_initialized = false; + m_close_event_received = false; + + init(); +} + +ProgressBarAndroid::~ProgressBarAndroid() +{ + close(); +} + +bool ProgressBarAndroid::compileShaders() +{ + const GLchar* vsh = + "precision mediump float;" + "attribute vec2 position;" + "uniform float progress;" + "void main(void)" + "{" + " float pos_x = (position.x + 1.0) * progress - 1.0;" + " float pos_y = position.y * 0.1 - 0.6;" + " gl_Position = vec4(pos_x, pos_y, 0.0, 1.0);" + "}"; + + const GLchar* fsh = + "precision mediump float;" + "void main(void)" + "{" + " gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);" + "}"; + + m_vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(m_vertex_shader, 1, &vsh, NULL); + glCompileShader(m_vertex_shader); + + GLint success; + glGetShaderiv(m_vertex_shader, GL_COMPILE_STATUS, &success); + if (!success) + { + Log::error("ProgressBarAndroid", "Failed to compile vertex shader."); + return false; + } + + m_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(m_fragment_shader, 1, &fsh, NULL); + glCompileShader(m_fragment_shader); + + glGetShaderiv(m_fragment_shader, GL_COMPILE_STATUS, &success); + if (!success) + { + Log::error("ProgressBarAndroid", "Failed to compile fragment shader."); + return false; + } + + m_program = glCreateProgram(); + + glAttachShader(m_program, m_vertex_shader); + glAttachShader(m_program, m_fragment_shader); + glLinkProgram(m_program); + + glGetProgramiv(m_program, GL_LINK_STATUS, &success); + if (!success) + { + Log::error("ProgressBarAndroid", "Failed to link program."); + return false; + } + + m_position = glGetAttribLocation(m_program, "position"); + if (m_position == -1) + { + Log::error("ProgressBarAndroid", "Failed to get attrib location."); + return false; + } + + m_progress = glGetUniformLocation(m_program, "progress"); + if (m_progress == -1) + { + Log::error("ProgressBarAndroid", "Failed to get uniform location."); + return false; + } + + return true; +} + +void ProgressBarAndroid::deleteShaders() +{ + glDeleteShader(m_vertex_shader); + glDeleteShader(m_fragment_shader); + glDeleteProgram(m_program); +} + +void ProgressBarAndroid::init() +{ + SIrrlichtCreationParameters params; + params.DriverType = video::EDT_OGLES2; + params.Bits = 32; + params.Fullscreen = UserConfigParams::m_fullscreen; + params.WindowSize = core::dimension2du(640, 480); +#if defined(ANDROID) + params.PrivateData = (void*)global_android_app; +#endif + + m_device = createDeviceEx(params); + + if (!m_device) + return; + + bool success = compileShaders(); + + if (!success) + return; + + const GLfloat vertices[] = + { + -1, 1, 1, -1, -1, -1, + 1, -1, -1, 1, 1, 1 + }; + + glGenBuffers(1, &m_vbo); + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glUseProgram(m_program); + glEnableVertexAttribArray(m_position); + glVertexAttribPointer(m_position, 2, GL_FLOAT, GL_FALSE, 0, 0); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + m_initialized = true; +} + +void ProgressBarAndroid::close() +{ + glDisableVertexAttribArray(m_position); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glUseProgram(0); + + deleteShaders(); + + if (m_device != NULL) + { + m_device->closeDevice(); + m_device->clearSystemMessages(); + m_device->run(); + m_device->drop(); + m_device = NULL; + } + + m_initialized = false; +} + +void ProgressBarAndroid::draw(float value) +{ + if (!m_initialized || m_close_event_received) + return; + + value = value > 1.0f ? 1.0f : value; + + m_close_event_received = !m_device->run(); + + m_device->getVideoDriver()->beginScene(true, true); + + glClear(GL_COLOR_BUFFER_BIT); + glUniform1f(m_progress, value); + glDrawArrays(GL_TRIANGLES, 0, 6); + + m_device->getVideoDriver()->endScene(); +} + +#endif diff --git a/src/utils/progress_bar_android.hpp b/src/utils/progress_bar_android.hpp new file mode 100644 index 000000000..b712457c4 --- /dev/null +++ b/src/utils/progress_bar_android.hpp @@ -0,0 +1,55 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_PROGRESS_BAR_ANDROID_HPP +#define HEADER_PROGRESS_BAR_ANDROID_HPP + +#ifdef ANDROID + +#include "IrrlichtDevice.h" +#include "graphics/gl_headers.hpp" + +class ProgressBarAndroid +{ +private: + GLuint m_program; + GLuint m_vertex_shader; + GLuint m_fragment_shader; + GLint m_position; + GLint m_progress; + GLuint m_vbo; + + irr::IrrlichtDevice* m_device; + bool m_initialized; + bool m_close_event_received; + + bool compileShaders(); + void deleteShaders(); + void init(); + void close(); + +public: + ProgressBarAndroid(); + ~ProgressBarAndroid(); + + void draw(float value); + bool closeEventReceived() {return m_close_event_received;} +}; + +#endif + +#endif From 56b6f81eb732c57450f4e6a86004459790b9a969 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Sun, 29 Jan 2017 19:58:33 -0500 Subject: [PATCH 014/378] New smooth camera --- src/graphics/camera_normal.cpp | 48 ++++++++++++++-------------------- src/graphics/camera_normal.hpp | 4 +-- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/graphics/camera_normal.cpp b/src/graphics/camera_normal.cpp index 93d818c58..d8a8b2952 100644 --- a/src/graphics/camera_normal.cpp +++ b/src/graphics/camera_normal.cpp @@ -38,11 +38,10 @@ */ CameraNormal::CameraNormal(Camera::CameraType type, int camera_index, AbstractKart* kart) - : Camera(type, camera_index, kart) + : Camera(type, camera_index, kart), m_camera_offset(0, 0, -15.0f) { m_distance = kart ? kart->getKartProperties()->getCameraDistance() : 1000.0f; m_ambient_light = Track::getCurrentTrack()->getDefaultAmbientColor(); - m_smooth_dt = 0.0f; // TODO: Put these values into a config file // Global or per split screen zone? @@ -88,44 +87,35 @@ void CameraNormal::smoothMoveCamera(float dt) float skid_factor = ks->getVisualSkidRotation(); float skid_angle = asin(skid_factor); - float ratio = (current_speed - max_speed_without_zipper) / max_increase_with_zipper; + float ratio = current_speed / max_speed_without_zipper; + ratio = ratio > -0.12f ? ratio : -0.12f; // distance of camera from kart in x and z plane - float camera_distance = -3 * (0.5f + ratio); - if (camera_distance > -2.0f) camera_distance = -2.0f; + float camera_distance = -4.0f * ratio; + if (camera_distance > -2.0f) camera_distance = -2.0f; // don't get too close to the kart // Defines how far camera should be from player kart. - Vec3 camera_offset(camera_distance * sin(skid_angle / 2), + Vec3 wanted_camera_offset(camera_distance * sin(skid_angle / 2), 1.1f * (1 + ratio / 2), camera_distance * cos(skid_angle / 2)); - Vec3 m_kart_camera_position_with_offset = m_kart->getTrans()(camera_offset); + + + //m_smooth_dt = 0.3f * dt + 0.7f * m_smooth_dt; + float delta = (dt*5.0f); + if (delta < 0.0f) + delta = 0.0f; + else if (delta > 1.0f) + delta = 1.0f; + + m_camera_offset += (wanted_camera_offset - m_camera_offset) * delta; + + Vec3 m_kart_camera_position_with_offset = m_kart->getTrans()(m_camera_offset); // next target Vec3 current_target = m_kart->getTrans()(Vec3(0, 0.5f, 0)); // new required position of camera - core::vector3df wanted_position = m_kart_camera_position_with_offset.toIrrVector(); - - float f = 5.0f; - if ((current_speed > 5 ) || (current_speed < 0 )) - { - f = current_speed >0 ? current_speed/3 + 1.0f - : -1.5f * current_speed + 2.0f; - } - m_smooth_dt = 0.3f * dt + 0.7f * m_smooth_dt; - current_position += (wanted_position - current_position) * (m_smooth_dt*f); - - // Avoid camera crash: if the speed is negative, the current_position - // can oscillate between plus and minus, getting bigger and bigger. If - // this happens often enough, floating point overflow happens (large - // negative speeds can happen when the kart is tumbling/falling) - // To avoid this, we just move the camera to the wanted position if - // the distance becomes too large (see #1356). - if( (current_position - wanted_position).getLengthSQ() > 100) - { - Log::debug("camera", "Resetting camera position to avoid crash"); - current_position = wanted_position; - } + current_position = m_kart_camera_position_with_offset.toIrrVector(); if(getMode()!=CM_FALLING) m_camera->setPosition(current_position); diff --git a/src/graphics/camera_normal.hpp b/src/graphics/camera_normal.hpp index 9185986cb..73d98d33d 100644 --- a/src/graphics/camera_normal.hpp +++ b/src/graphics/camera_normal.hpp @@ -49,8 +49,8 @@ private: /** Factor of the effects of steering in camera aim. */ float m_rotation_range; - /** Used to smoothly move the camera. */ - float m_smooth_dt; + Vec3 m_camera_offset; + void smoothMoveCamera(float dt); void handleEndCamera(float dt); void getCameraSettings(float *above_kart, float *cam_angle, From 6d3eb84a8e4b845fa66a653f723202ca0df1db15 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Sun, 29 Jan 2017 21:33:11 -0500 Subject: [PATCH 015/378] Reduce shaking a bit by smoothing kart position. Still need to smooth kart rotation, can't quite get it to work yet --- src/graphics/camera_normal.cpp | 42 +++++++++++++++++++++++++++------- src/graphics/camera_normal.hpp | 3 +++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/graphics/camera_normal.cpp b/src/graphics/camera_normal.cpp index d8a8b2952..35851820f 100644 --- a/src/graphics/camera_normal.cpp +++ b/src/graphics/camera_normal.cpp @@ -54,6 +54,10 @@ CameraNormal::CameraNormal(Camera::CameraType type, int camera_index, m_rotation_range = 0.0f; reset(); m_camera->setNearValue(1.0f); + + btTransform btt = kart->getTrans(); + m_kart_position = btt.getOrigin(); + m_kart_rotation = btt.getRotation(); } // Camera //----------------------------------------------------------------------------- @@ -68,15 +72,15 @@ void CameraNormal::smoothMoveCamera(float dt) if (kart->isFlying()) { Vec3 vec3 = m_kart->getXYZ() + Vec3(sin(m_kart->getHeading()) * -4.0f, - 0.5f, - cos(m_kart->getHeading()) * -4.0f); + 0.5f, + cos(m_kart->getHeading()) * -4.0f); m_camera->setTarget(m_kart->getXYZ().toIrrVector()); m_camera->setPosition(vec3.toIrrVector()); return; } // kart is flying - core::vector3df current_position = m_camera->getPosition(); + core::vector3df current_position = m_camera->getPosition(); // Smoothly interpolate towards the position and target const KartProperties *kp = m_kart->getKartProperties(); float max_increase_with_zipper = kp->getZipperMaxSpeedIncrease(); @@ -92,13 +96,13 @@ void CameraNormal::smoothMoveCamera(float dt) ratio = ratio > -0.12f ? ratio : -0.12f; // distance of camera from kart in x and z plane - float camera_distance = -4.0f * ratio; + float camera_distance = -2.5f * ratio; if (camera_distance > -2.0f) camera_distance = -2.0f; // don't get too close to the kart // Defines how far camera should be from player kart. Vec3 wanted_camera_offset(camera_distance * sin(skid_angle / 2), - 1.1f * (1 + ratio / 2), - camera_distance * cos(skid_angle / 2)); + 1.1f * (1 + ratio / 2), + camera_distance * cos(skid_angle / 2)); //m_smooth_dt = 0.3f * dt + 0.7f * m_smooth_dt; @@ -110,13 +114,35 @@ void CameraNormal::smoothMoveCamera(float dt) m_camera_offset += (wanted_camera_offset - m_camera_offset) * delta; - Vec3 m_kart_camera_position_with_offset = m_kart->getTrans()(m_camera_offset); + float delta2 = dt * 8.0f; + if (delta2 < 0) + delta2 = 0; + else if (delta2 > 1) + delta2 = 1; + btTransform btt = m_kart->getTrans(); + m_kart_position = m_kart_position + (btt.getOrigin() - m_kart_position) * delta2; + + // TODO + //m_kart_rotation = m_kart_rotation.slerp(btt.getRotation(), delta2); + m_kart_rotation = btt.getRotation(); + + btt.setOrigin(m_kart_position); + btt.setRotation(m_kart_rotation); + + Vec3 m_kart_camera_position_with_offset = btt(m_camera_offset); // next target - Vec3 current_target = m_kart->getTrans()(Vec3(0, 0.5f, 0)); + Vec3 current_target = btt(Vec3(0, 0.5f, 0)); // new required position of camera current_position = m_kart_camera_position_with_offset.toIrrVector(); + + //Log::info("CAM_DEBUG", "OFFSET: %f %f %f TRANSFORMED %f %f %f TARGET %f %f %f", + // wanted_camera_offset.x(), wanted_camera_offset.y(), wanted_camera_offset.z(), + // m_kart_camera_position_with_offset.x(), m_kart_camera_position_with_offset.y(), + // m_kart_camera_position_with_offset.z(), current_target.x(), current_target.y(), + // current_target.z()); + if(getMode()!=CM_FALLING) m_camera->setPosition(current_position); m_camera->setTarget(current_target.toIrrVector());//set new target diff --git a/src/graphics/camera_normal.hpp b/src/graphics/camera_normal.hpp index 73d98d33d..f80a01626 100644 --- a/src/graphics/camera_normal.hpp +++ b/src/graphics/camera_normal.hpp @@ -59,6 +59,9 @@ private: void positionCamera(float dt, float above_kart, float cam_angle, float side_way, float distance, float smoothing); + btVector3 m_kart_position; + btQuaternion m_kart_rotation; + // Give a few classes access to the constructor (mostly for inheritance) friend class Camera; friend class CameraDebug; From e8cb873f083fae12f485785d55aea8b5a68e831c Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Sun, 29 Jan 2017 21:37:58 -0500 Subject: [PATCH 016/378] Finish smoothing the kart rotation --- src/graphics/camera_normal.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/graphics/camera_normal.cpp b/src/graphics/camera_normal.cpp index 35851820f..5164d65da 100644 --- a/src/graphics/camera_normal.cpp +++ b/src/graphics/camera_normal.cpp @@ -122,10 +122,7 @@ void CameraNormal::smoothMoveCamera(float dt) btTransform btt = m_kart->getTrans(); m_kart_position = m_kart_position + (btt.getOrigin() - m_kart_position) * delta2; - - // TODO - //m_kart_rotation = m_kart_rotation.slerp(btt.getRotation(), delta2); - m_kart_rotation = btt.getRotation(); + m_kart_rotation = m_kart_rotation.normalized().slerp(btt.getRotation().normalized(), delta2); btt.setOrigin(m_kart_position); btt.setRotation(m_kart_rotation); From e7252cc643e1804f43d9add040d78126ec875e15 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Sun, 29 Jan 2017 21:48:54 -0500 Subject: [PATCH 017/378] More work on camera smoothness --- src/graphics/camera_normal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/camera_normal.cpp b/src/graphics/camera_normal.cpp index 5164d65da..23df6ceae 100644 --- a/src/graphics/camera_normal.cpp +++ b/src/graphics/camera_normal.cpp @@ -96,7 +96,7 @@ void CameraNormal::smoothMoveCamera(float dt) ratio = ratio > -0.12f ? ratio : -0.12f; // distance of camera from kart in x and z plane - float camera_distance = -2.5f * ratio; + float camera_distance = -4.0f * ratio; if (camera_distance > -2.0f) camera_distance = -2.0f; // don't get too close to the kart // Defines how far camera should be from player kart. @@ -121,7 +121,7 @@ void CameraNormal::smoothMoveCamera(float dt) delta2 = 1; btTransform btt = m_kart->getTrans(); - m_kart_position = m_kart_position + (btt.getOrigin() - m_kart_position) * delta2; + m_kart_position = btt.getOrigin();// m_kart_position + (btt.getOrigin() - m_kart_position) * delta2; m_kart_rotation = m_kart_rotation.normalized().slerp(btt.getRotation().normalized(), delta2); btt.setOrigin(m_kart_position); From bf6445945836322f573823ee453fddbb8e84cf4a Mon Sep 17 00:00:00 2001 From: deve Date: Mon, 30 Jan 2017 11:18:31 +0100 Subject: [PATCH 018/378] Handle a case that advanced lighting is disabled in graphics restrictions. --- src/graphics/material.cpp | 6 +++--- src/graphics/referee.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/graphics/material.cpp b/src/graphics/material.cpp index 4843661e5..a6b5c66db 100644 --- a/src/graphics/material.cpp +++ b/src/graphics/material.cpp @@ -76,7 +76,7 @@ Material::Material(const XMLNode *node, bool deprecated) init(); bool b = false; - + node->get("clampu", &b); if (b) m_clamp_tex |= UCLAMP; //blender 2.4 style node->get("clampU", &b); if (b) m_clamp_tex |= UCLAMP; //blender 2.5 style b = false; @@ -745,7 +745,7 @@ void Material::setMaterialProperties(video::SMaterial *m, scene::IMeshBuffer* m ITexture *tex; ITexture *glossytex; STKTexManager* stm = STKTexManager::getInstance(); - if (m_gloss_map.size() > 0 && UserConfigParams::m_dynamic_lights) + if (m_gloss_map.size() > 0 && CVS->isDefferedEnabled()) { glossytex = stm->getTexture(m_gloss_map, false/*srgb*/, false/*premul_alpha*/, false/*set_material*/, @@ -886,7 +886,7 @@ void Material::setMaterialProperties(video::SMaterial *m, scene::IMeshBuffer* m if (m_normal_map_tex.size() > 0) { - if (UserConfigParams::m_dynamic_lights) + if (CVS->isDefferedEnabled()) { tex = stm->getTexture(m_normal_map_tex, false/*srgb*/, false/*premul_alpha*/, false/*set_material*/, diff --git a/src/graphics/referee.cpp b/src/graphics/referee.cpp index 3e1cd8962..e00273318 100644 --- a/src/graphics/referee.cpp +++ b/src/graphics/referee.cpp @@ -237,7 +237,7 @@ void Referee::selectReadySetGo(int rsg) return; video::SMaterial &m = m_scene_node->getMaterial(m_st_traffic_buffer); // m_scene_node->getMesh()->getMeshBuffer(m_st_traffic_buffer)->getMaterial(); - //if (irr_driver->isGLSL() && UserConfigParams::m_dynamic_lights) + //if (irr_driver->isGLSL() && CVS->isDefferedEnabled()) // m.MaterialType = irr_driver->getShader(ES_OBJECT_UNLIT); core::matrix4* matrix = &m.getTextureMatrix(0); From fbffb108f108ef2d38f60080fc090834c01eded2 Mon Sep 17 00:00:00 2001 From: Deve Date: Mon, 30 Jan 2017 23:43:13 +0100 Subject: [PATCH 019/378] Force to use rtts when scale_rtts_factor is set. It allows to scale down resolution even if advanced lighting is disabled. --- src/graphics/shader_based_renderer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/graphics/shader_based_renderer.cpp b/src/graphics/shader_based_renderer.cpp index 287717f5b..99071fc83 100644 --- a/src/graphics/shader_based_renderer.cpp +++ b/src/graphics/shader_based_renderer.cpp @@ -787,7 +787,9 @@ void ShaderBasedRenderer::render(float dt) RaceGUIBase *rg = world->getRaceGUI(); if (rg) rg->update(dt); - if (!CVS->isDefferedEnabled()) + bool force_rtt = UserConfigParams::m_scale_rtts_factor != 1.0f; + + if (!CVS->isDefferedEnabled() && !force_rtt) { prepareForwardRenderer(); } @@ -801,7 +803,7 @@ void ShaderBasedRenderer::render(float dt) oss << "drawAll() for kart " << cam; PROFILER_PUSH_CPU_MARKER(oss.str().c_str(), (cam+1)*60, 0x00, 0x00); - camera->activate(!CVS->isDefferedEnabled()); + camera->activate(!CVS->isDefferedEnabled() && !force_rtt); rg->preRenderCallback(camera); // adjusts start referee irr_driver->getSceneManager()->setActiveCamera(camnode); @@ -819,7 +821,7 @@ void ShaderBasedRenderer::render(float dt) if(CVS->isARBUniformBufferObjectUsable()) uploadLightingData(); PROFILER_POP_CPU_MARKER(); - renderScene(camnode, dt, track->hasShadows(), false); + renderScene(camnode, dt, track->hasShadows(), force_rtt); if (irr_driver->getBoundingBoxesViz()) { @@ -828,7 +830,7 @@ void ShaderBasedRenderer::render(float dt) debugPhysics(); - if (CVS->isDefferedEnabled()) + if (CVS->isDefferedEnabled() || force_rtt) { renderPostProcessing(camera); } From 93d6b1e515df6714491a83c62cd1e2c035bb05c3 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Mon, 30 Jan 2017 19:17:27 -0500 Subject: [PATCH 020/378] Tweak camera settings --- src/graphics/camera_normal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/camera_normal.cpp b/src/graphics/camera_normal.cpp index 23df6ceae..73c9d2209 100644 --- a/src/graphics/camera_normal.cpp +++ b/src/graphics/camera_normal.cpp @@ -96,12 +96,12 @@ void CameraNormal::smoothMoveCamera(float dt) ratio = ratio > -0.12f ? ratio : -0.12f; // distance of camera from kart in x and z plane - float camera_distance = -4.0f * ratio; + float camera_distance = -1.25f - 2.5f * ratio; if (camera_distance > -2.0f) camera_distance = -2.0f; // don't get too close to the kart // Defines how far camera should be from player kart. Vec3 wanted_camera_offset(camera_distance * sin(skid_angle / 2), - 1.1f * (1 + ratio / 2), + (0.85f + ratio / 2.5f), camera_distance * cos(skid_angle / 2)); From 3c8369b915f13b8c638046ed5f45c5118219ed36 Mon Sep 17 00:00:00 2001 From: deve Date: Wed, 1 Feb 2017 10:30:46 +0100 Subject: [PATCH 021/378] Set GLSL version for GLES. It's not 100% true because GLES has different versions numbering. For example it doesn't have geometry shaders even if they were introduced in GLSL 1.50 (OpenGL 3.2). But still in this way we make sure that it uses the same features on all devices, no mater if it's GLES 3.0, 3.1, 3.2 etc. --- src/graphics/central_settings.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index 0da23537f..0851ef82f 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -91,7 +91,7 @@ void CentralVideoSettings::init() std::string driver((char*)(glGetString(GL_VERSION))); std::string card((char*)(glGetString(GL_RENDERER))); GraphicsRestrictions::init(driver, card); - + if (GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_FORCE_LEGACY_DEVICE)) { m_glsl = false; @@ -259,12 +259,19 @@ void CentralVideoSettings::init() unsigned CentralVideoSettings::getGLSLVersion() const { +#if defined(USE_GLES2) + if (m_gl_major_version >= 3) + return 300; + else + return 100; +#else if (m_gl_major_version > 3 || (m_gl_major_version == 3 && m_gl_minor_version == 3)) return m_gl_major_version * 100 + m_gl_minor_version * 10; else if (m_gl_major_version == 3) return 100 + (m_gl_minor_version + 3) * 10; else return 120; +#endif } bool CentralVideoSettings::isGLSL() const From d124c61e3c271dfb9f26d6e3ff33ec133e7bf245 Mon Sep 17 00:00:00 2001 From: deve Date: Wed, 1 Feb 2017 10:40:48 +0100 Subject: [PATCH 022/378] Remove useless ifdef. It should depend on features available in graphics drivers and not on headers that were used for compilation. In theory there was a possible case that GL_VERSION_3_3 was not defined and CVS->getGLSLVersion() >= 330 was true, so that bind wasn't done at all. And still GL_VERSION_3_3 should be always true for OpenGL renderer because it's defined in glew. --- src/graphics/texture_shader.cpp | 14 -------------- src/graphics/texture_shader.hpp | 10 ++++------ 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/graphics/texture_shader.cpp b/src/graphics/texture_shader.cpp index 339434ae8..48c11e17f 100644 --- a/src/graphics/texture_shader.cpp +++ b/src/graphics/texture_shader.cpp @@ -248,7 +248,6 @@ GLuint TextureShaderBase::createSamplers(SamplerTypeNew sampler_type) // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createNearestSampler() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -258,13 +257,11 @@ GLuint TextureShaderBase::createNearestSampler() if (CVS->isEXTTextureFilterAnisotropicUsable()) glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.); return id; -#endif } // createNearestSampler // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createTrilinearSampler() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -279,13 +276,11 @@ GLuint TextureShaderBase::createTrilinearSampler() glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso); } return id; -#endif } // createTrilinearSampler // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createBilinearSampler() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -295,12 +290,10 @@ GLuint TextureShaderBase::createBilinearSampler() if (CVS->isEXTTextureFilterAnisotropicUsable()) glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.); return id; -#endif } // createBilinearSampler // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createShadowSampler() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -310,13 +303,11 @@ GLuint TextureShaderBase::createShadowSampler() glSamplerParameterf(id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glSamplerParameterf(id, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); return id; -#endif } // createShadowSampler // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createBilinearClampedSampler() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -326,14 +317,12 @@ GLuint TextureShaderBase::createBilinearClampedSampler() if (CVS->isEXTTextureFilterAnisotropicUsable()) glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.); return id; -#endif } // createBilinearClampedSampler // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createTrilinearClampedArray() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -348,12 +337,10 @@ GLuint TextureShaderBase::createTrilinearClampedArray() glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso); } return id; -#endif } // createTrilinearClampedArray // ---------------------------------------------------------------------------- GLuint TextureShaderBase::createSemiTrilinearSampler() { -#ifdef GL_VERSION_3_3 unsigned id; glGenSamplers(1, &id); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -363,7 +350,6 @@ GLuint TextureShaderBase::createSemiTrilinearSampler() if (CVS->isEXTTextureFilterAnisotropicUsable()) glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.); return id; -#endif } // createSemiTrilinearSampler // ---------------------------------------------------------------------------- diff --git a/src/graphics/texture_shader.hpp b/src/graphics/texture_shader.hpp index 5c1c3a128..7707448c5 100644 --- a/src/graphics/texture_shader.hpp +++ b/src/graphics/texture_shader.hpp @@ -48,8 +48,8 @@ enum SamplerTypeNew // ============================================================================ /** A simple non-templated base class for a shader that uses textures. A non - * templated base class is necessary to easily handle static objects (like - * list of all bind functions to call) - with templates each instance is a + * templated base class is necessary to easily handle static objects (like + * list of all bind functions to call) - with templates each instance is a * different class (with different static values). */ class TextureShaderBase @@ -68,7 +68,7 @@ protected: static void bindTextureShadow(GLuint tex_unit, GLuint tex_id); static void bindTrilinearClampedArrayTexture(GLuint tex_unit, GLuint tex_id); static void bindTextureVolume(GLuint tex_unit, GLuint tex_id); - + GLuint createSamplers(SamplerTypeNew sampler_type); private: @@ -88,7 +88,7 @@ protected: // ======================================================================== /** Class C needs to be the newly declared shaders class (necessary for * the instance template). NUM_TEXTURES is the number of texture units - * used in this shader. It is used to test at compile time that the + * used in this shader. It is used to test at compile time that the * right number of arguments are supplied to the variadic functions. */ template @@ -173,11 +173,9 @@ public: { if (CVS->getGLSLVersion() >= 330) { -#ifdef GL_VERSION_3_3 glActiveTexture(GL_TEXTURE0 + m_texture_units[N]); glBindTexture(m_texture_type[N], tex_id); glBindSampler(m_texture_units[N], m_sampler_ids[N]); -#endif } else { From 28d85d7ba356d2cb15a857b13d2127ade027ed88 Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 1 Feb 2017 21:58:10 +0100 Subject: [PATCH 023/378] Use explicit attrib location when the extension is available. It allows to enable it easily in GLES renderer. And we check if this extension is available anyway because it's needed for shadows, so we can use it for other shaders too. --- data/shaders/billboard.vert | 2 +- data/shaders/coloredquad.vert | 4 ++-- data/shaders/colortexturedquad.vert | 2 +- data/shaders/displace.vert | 2 +- data/shaders/flipparticle.vert | 2 +- data/shaders/glow_object.vert | 2 +- data/shaders/grass_pass.vert | 2 +- data/shaders/instanced_grass.vert | 2 +- data/shaders/instanced_grassshadow.vert | 2 +- data/shaders/instanced_object_pass.vert | 2 +- data/shaders/instanced_shadow.vert | 2 +- data/shaders/instanced_skinning.vert | 2 +- data/shaders/instanced_skinning_shadow.vert | 2 +- data/shaders/object_pass.vert | 2 +- data/shaders/particle.vert | 2 +- data/shaders/particlesimheightmap.vert | 2 +- data/shaders/pointemitter.vert | 14 +++++++------- data/shaders/primitive2dlist.vert | 2 +- data/shaders/rsm.vert | 2 +- data/shaders/screenquad.vert | 2 +- data/shaders/shadow.vert | 2 +- data/shaders/shadow_grass.vert | 2 +- data/shaders/skinning.vert | 2 +- data/shaders/skinning_shadow.vert | 2 +- data/shaders/sky.vert | 2 +- data/shaders/texturedquad.vert | 4 ++-- src/graphics/central_settings.cpp | 6 ++++++ src/graphics/shader.cpp | 4 ++-- src/graphics/shader.hpp | 6 +++--- src/graphics/shader_files_manager.cpp | 3 +++ 30 files changed, 48 insertions(+), 39 deletions(-) diff --git a/data/shaders/billboard.vert b/data/shaders/billboard.vert index a6eb64557..06882526d 100644 --- a/data/shaders/billboard.vert +++ b/data/shaders/billboard.vert @@ -2,7 +2,7 @@ uniform mat4 ModelViewMatrix; uniform vec3 Position; uniform vec2 Size; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec2 Corner; layout(location = 3) in vec2 Texcoord; #else diff --git a/data/shaders/coloredquad.vert b/data/shaders/coloredquad.vert index 4f48e98f2..ab0095d52 100644 --- a/data/shaders/coloredquad.vert +++ b/data/shaders/coloredquad.vert @@ -1,7 +1,7 @@ uniform vec2 center; uniform vec2 size; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec2 Position; #else in vec2 Position; @@ -11,4 +11,4 @@ in vec2 Position; void main() { gl_Position = vec4(Position * size + center, 0., 1.); -} \ No newline at end of file +} diff --git a/data/shaders/colortexturedquad.vert b/data/shaders/colortexturedquad.vert index 467fdd080..06bd61b40 100644 --- a/data/shaders/colortexturedquad.vert +++ b/data/shaders/colortexturedquad.vert @@ -3,7 +3,7 @@ uniform vec2 size; uniform vec2 texcenter; uniform vec2 texsize; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location=0) in vec2 Position; layout(location=3) in vec2 Texcoord; layout(location=2) in uvec4 Color; diff --git a/data/shaders/displace.vert b/data/shaders/displace.vert index d82ac8824..e5acc43b5 100644 --- a/data/shaders/displace.vert +++ b/data/shaders/displace.vert @@ -1,6 +1,6 @@ uniform mat4 ModelMatrix; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 3) in vec2 Texcoord; layout(location = 4) in vec2 SecondTexcoord; diff --git a/data/shaders/flipparticle.vert b/data/shaders/flipparticle.vert index 7f43b0763..4c81d8082 100644 --- a/data/shaders/flipparticle.vert +++ b/data/shaders/flipparticle.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location=0) in vec3 Position; layout(location = 1) in float lifetime; layout(location = 2) in float size; diff --git a/data/shaders/glow_object.vert b/data/shaders/glow_object.vert index 78cd0279a..552c978bc 100644 --- a/data/shaders/glow_object.vert +++ b/data/shaders/glow_object.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 7) in vec3 Origin; layout(location = 8) in vec3 Orientation; diff --git a/data/shaders/grass_pass.vert b/data/shaders/grass_pass.vert index 08d44fff6..23daa553c 100644 --- a/data/shaders/grass_pass.vert +++ b/data/shaders/grass_pass.vert @@ -2,7 +2,7 @@ uniform vec3 windDir; uniform mat4 ModelMatrix; uniform mat4 InverseModelMatrix; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/instanced_grass.vert b/data/shaders/instanced_grass.vert index dcfba88a5..57adee847 100644 --- a/data/shaders/instanced_grass.vert +++ b/data/shaders/instanced_grass.vert @@ -1,6 +1,6 @@ uniform vec3 windDir; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/instanced_grassshadow.vert b/data/shaders/instanced_grassshadow.vert index e8d31d927..5d5e8e6e2 100644 --- a/data/shaders/instanced_grassshadow.vert +++ b/data/shaders/instanced_grassshadow.vert @@ -1,7 +1,7 @@ uniform int layer; uniform vec3 windDir; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 2) in vec4 Color; layout(location = 3) in vec2 Texcoord; diff --git a/data/shaders/instanced_object_pass.vert b/data/shaders/instanced_object_pass.vert index de0befb04..4cffcaeb1 100644 --- a/data/shaders/instanced_object_pass.vert +++ b/data/shaders/instanced_object_pass.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/instanced_shadow.vert b/data/shaders/instanced_shadow.vert index 09885f333..6aa25655a 100644 --- a/data/shaders/instanced_shadow.vert +++ b/data/shaders/instanced_shadow.vert @@ -1,6 +1,6 @@ uniform int layer; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 3) in vec2 Texcoord; diff --git a/data/shaders/instanced_skinning.vert b/data/shaders/instanced_skinning.vert index 6802642a4..607bfc4f9 100644 --- a/data/shaders/instanced_skinning.vert +++ b/data/shaders/instanced_skinning.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/instanced_skinning_shadow.vert b/data/shaders/instanced_skinning_shadow.vert index 21e4a5294..7f72e56fb 100644 --- a/data/shaders/instanced_skinning_shadow.vert +++ b/data/shaders/instanced_skinning_shadow.vert @@ -1,6 +1,6 @@ uniform int layer; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 3) in vec4 Data1; layout(location = 5) in ivec4 Joint; diff --git a/data/shaders/object_pass.vert b/data/shaders/object_pass.vert index dcd6a3985..124e8c26e 100644 --- a/data/shaders/object_pass.vert +++ b/data/shaders/object_pass.vert @@ -17,7 +17,7 @@ uniform mat4 InverseModelMatrix = uniform vec2 texture_trans = vec2(0., 0.); #endif -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/particle.vert b/data/shaders/particle.vert index 78d32ef29..d9a7d8659 100644 --- a/data/shaders/particle.vert +++ b/data/shaders/particle.vert @@ -1,7 +1,7 @@ uniform vec3 color_from; uniform vec3 color_to; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location=0) in vec3 Position; layout(location = 1) in float lifetime; layout(location = 2) in float size; diff --git a/data/shaders/particlesimheightmap.vert b/data/shaders/particlesimheightmap.vert index eec3151c1..5095c837b 100644 --- a/data/shaders/particlesimheightmap.vert +++ b/data/shaders/particlesimheightmap.vert @@ -9,7 +9,7 @@ uniform float track_x_len; uniform float track_z_len; uniform samplerBuffer heightmap; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout (location = 4) in vec3 particle_position_initial; layout (location = 5) in float lifetime_initial; layout (location = 6) in vec3 particle_velocity_initial; diff --git a/data/shaders/pointemitter.vert b/data/shaders/pointemitter.vert index 54e5b58ca..60d150b28 100644 --- a/data/shaders/pointemitter.vert +++ b/data/shaders/pointemitter.vert @@ -4,7 +4,7 @@ uniform mat4 sourcematrix; uniform int level; uniform float size_increase_factor; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout (location = 4) in vec3 particle_position_initial; layout (location = 5) in float lifetime_initial; layout (location = 6) in vec3 particle_velocity_initial; @@ -40,14 +40,14 @@ void main(void) { float dt_from_last_frame = fract(updated_lifetime) * lifetime_initial; float coeff = dt_from_last_frame / float(dt); - + vec4 previous_frame_position = previous_frame_sourcematrix * vec4(particle_position_initial, 1.0); vec4 current_frame_position = sourcematrix * vec4(particle_position_initial, 1.0); - + vec4 updated_initialposition = mix(current_frame_position, previous_frame_position, coeff); - + vec4 updated_initial_velocity = mix(sourcematrix * vec4(particle_velocity_initial, 0.0), previous_frame_sourcematrix * vec4(particle_velocity_initial, 0.0), coeff); @@ -56,12 +56,12 @@ void main(void) //But the simple formula ( (current_frame_position - previous_frame_position) / dt ) with a constant speed //between 2 frames creates visual artifacts when the framerate is low, and a more accurate formula would need //more complex computations. - + new_particle_position = updated_initialposition.xyz + dt_from_last_frame * updated_initial_velocity.xyz; new_particle_velocity = updated_initial_velocity.xyz; - + new_lifetime = fract(updated_lifetime); - new_size = mix(size_initial, size_initial * size_increase_factor, fract(updated_lifetime)); + new_size = mix(size_initial, size_initial * size_increase_factor, fract(updated_lifetime)); } else { diff --git a/data/shaders/primitive2dlist.vert b/data/shaders/primitive2dlist.vert index 7a2c65fa0..791647c20 100644 --- a/data/shaders/primitive2dlist.vert +++ b/data/shaders/primitive2dlist.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/rsm.vert b/data/shaders/rsm.vert index a02e62b30..6029753ed 100644 --- a/data/shaders/rsm.vert +++ b/data/shaders/rsm.vert @@ -2,7 +2,7 @@ uniform mat4 ModelMatrix; uniform mat4 RSMMatrix; uniform vec2 texture_trans = vec2(0., 0.); -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/screenquad.vert b/data/shaders/screenquad.vert index 6ece8e050..c77284492 100644 --- a/data/shaders/screenquad.vert +++ b/data/shaders/screenquad.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec2 Position; layout(location = 3) in vec2 Texcoord; #else diff --git a/data/shaders/shadow.vert b/data/shaders/shadow.vert index 3bbc6c6a5..7ecab2a84 100644 --- a/data/shaders/shadow.vert +++ b/data/shaders/shadow.vert @@ -1,7 +1,7 @@ uniform int layer; uniform mat4 ModelMatrix; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 3) in vec2 Texcoord; #else diff --git a/data/shaders/shadow_grass.vert b/data/shaders/shadow_grass.vert index bf6d7435b..95827b872 100644 --- a/data/shaders/shadow_grass.vert +++ b/data/shaders/shadow_grass.vert @@ -2,7 +2,7 @@ uniform int layer; uniform mat4 ModelMatrix; uniform vec3 windDir; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 2) in vec4 Color; layout(location = 3) in vec2 Texcoord; diff --git a/data/shaders/skinning.vert b/data/shaders/skinning.vert index e74fc508c..192f4247f 100644 --- a/data/shaders/skinning.vert +++ b/data/shaders/skinning.vert @@ -18,7 +18,7 @@ uniform vec2 texture_trans = vec2(0., 0.); #endif uniform int skinning_offset; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 1) in vec3 Normal; layout(location = 2) in vec4 Color; diff --git a/data/shaders/skinning_shadow.vert b/data/shaders/skinning_shadow.vert index 6e8d8fb3b..eca18ec1a 100644 --- a/data/shaders/skinning_shadow.vert +++ b/data/shaders/skinning_shadow.vert @@ -2,7 +2,7 @@ uniform mat4 ModelMatrix; uniform int skinning_offset; uniform int layer; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; layout(location = 3) in vec4 Data1; layout(location = 5) in ivec4 Joint; diff --git a/data/shaders/sky.vert b/data/shaders/sky.vert index eb254d521..e21bbbf41 100644 --- a/data/shaders/sky.vert +++ b/data/shaders/sky.vert @@ -1,4 +1,4 @@ -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location = 0) in vec3 Position; #else in vec3 Position; diff --git a/data/shaders/texturedquad.vert b/data/shaders/texturedquad.vert index 3299139c0..9637d054a 100644 --- a/data/shaders/texturedquad.vert +++ b/data/shaders/texturedquad.vert @@ -3,7 +3,7 @@ uniform vec2 size; uniform vec2 texcenter; uniform vec2 texsize; -#if __VERSION__ >= 330 +#ifdef Explicit_Attrib_Location_Usable layout(location=0) in vec2 Position; layout(location=3) in vec2 Texcoord; #else @@ -17,4 +17,4 @@ void main() { uv = Texcoord * texsize + texcenter; gl_Position = vec4(Position * size + center, 0., 1.); -} \ No newline at end of file +} diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index 0851ef82f..54c4fa294 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -238,6 +238,12 @@ void CentralVideoSettings::init() hasTextureStorage = true; hasTextureSwizzle = true; } + + if (!GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_EXPLICIT_ATTRIB_LOCATION) + { + Log::info("GLDriver", "Explicit Attrib Location Present"); + hasExplicitAttribLocation = true; + } if (!GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_TEXTURE_FORMAT_BGRA8888) && (hasGLExtension("GL_IMG_texture_format_BGRA8888") || diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index 89cf135f5..bb5cefe35 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -42,7 +42,7 @@ int ShaderBase::loadTFBProgram(const std::string &shader_name, #ifdef USE_GLES2 loadAndAttachShader(GL_FRAGMENT_SHADER, "tfb_dummy.frag"); #endif - if (CVS->getGLSLVersion() < 330) + if (!CVS->isARBExplicitAttribLocationUsable()) setAttribute(PARTICLES_SIM); glTransformFeedbackVaryings(m_program, varying_count, varyings, @@ -74,7 +74,7 @@ void ShaderBase::bypassUBO() const GLint PM = glGetUniformLocation(m_program, "ProjectionMatrix"); glUniformMatrix4fv(PM, 1, GL_FALSE, irr_driver->getProjMatrix().pointer()); - + GLint PVM = glGetUniformLocation(m_program, "ProjectionViewMatrix"); glUniformMatrix4fv(PVM, 1, GL_FALSE, irr_driver->getProjViewMatrix().pointer()); diff --git a/src/graphics/shader.hpp b/src/graphics/shader.hpp index 7df252576..302cc4370 100644 --- a/src/graphics/shader.hpp +++ b/src/graphics/shader.hpp @@ -33,7 +33,7 @@ #include #include -/** A simple non-templated base class. It is used to store some enums used in +/** A simple non-templated base class. It is used to store some enums used in * templates, the actual header for a shader, and a statis list of all kill * functions (which delete all singletons, and therefore forces a reload of all * shaders). @@ -301,7 +301,7 @@ public: * \param index Index of the texture. * \param uniform Uniform name. */ - template + template void assignTextureUnit(GLuint index, const char* uniform, T1... rest) { glUseProgram(m_program); @@ -349,7 +349,7 @@ public: { m_program = glCreateProgram(); loadAndAttachShader(args...); - if (CVS->getGLSLVersion() < 330) + if (!CVS->isARBExplicitAttribLocationUsable()) setAttribute(type); glLinkProgram(m_program); diff --git a/src/graphics/shader_files_manager.cpp b/src/graphics/shader_files_manager.cpp index 01c32b28e..e7b37de05 100644 --- a/src/graphics/shader_files_manager.cpp +++ b/src/graphics/shader_files_manager.cpp @@ -89,7 +89,10 @@ GLuint ShaderFilesManager::loadShader(const std::string &file, unsigned type) code << "#extension GL_AMD_vertex_shader_layer : enable\n"; if (CVS->isARBExplicitAttribLocationUsable()) + { code << "#extension GL_ARB_explicit_attrib_location : enable\n"; + code << "#define Explicit_Attrib_Location_Usable\n"; + } if (CVS->isAZDOEnabled()) { From b54c3648b3bfe72fc315504c19ff988bc248e988 Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 1 Feb 2017 22:15:48 +0100 Subject: [PATCH 024/378] Again missing parenthesis... --- src/graphics/central_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index 54c4fa294..53651fd36 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -239,7 +239,7 @@ void CentralVideoSettings::init() hasTextureSwizzle = true; } - if (!GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_EXPLICIT_ATTRIB_LOCATION) + if (!GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_EXPLICIT_ATTRIB_LOCATION)) { Log::info("GLDriver", "Explicit Attrib Location Present"); hasExplicitAttribLocation = true; From d4d54528254b3111dbdf3d6e54bad6ec7acfe38a Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 1 Feb 2017 22:52:07 +0100 Subject: [PATCH 025/378] Enable glBindSampler in GLES --- src/graphics/texture_shader.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/graphics/texture_shader.hpp b/src/graphics/texture_shader.hpp index 7707448c5..244c78b7e 100644 --- a/src/graphics/texture_shader.hpp +++ b/src/graphics/texture_shader.hpp @@ -171,7 +171,11 @@ public: template void setTextureUnitsImpl(GLuint tex_id, TexIds... args) { +#if defined(USE_GLES2) + if (CVS->getGLSLVersion() >= 300) +#else if (CVS->getGLSLVersion() >= 330) +#endif { glActiveTexture(GL_TEXTURE0 + m_texture_units[N]); glBindTexture(m_texture_type[N], tex_id); From 19cb9cd041559263a64825ccfa50ab4eec62c729 Mon Sep 17 00:00:00 2001 From: Deve Date: Thu, 2 Feb 2017 22:22:33 +0100 Subject: [PATCH 026/378] Fixed a crash when starting cutscene --- src/graphics/camera_normal.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/graphics/camera_normal.cpp b/src/graphics/camera_normal.cpp index 73c9d2209..26518b799 100644 --- a/src/graphics/camera_normal.cpp +++ b/src/graphics/camera_normal.cpp @@ -52,12 +52,17 @@ CameraNormal::CameraNormal(Camera::CameraType type, int camera_index, m_target_speed = 10.0f; m_rotation_range = 0.4f; m_rotation_range = 0.0f; + m_kart_position = btVector3(0, 0, 0); + m_kart_rotation = btQuaternion(0, 0, 0, 0); reset(); m_camera->setNearValue(1.0f); - btTransform btt = kart->getTrans(); - m_kart_position = btt.getOrigin(); - m_kart_rotation = btt.getRotation(); + if (kart) + { + btTransform btt = kart->getTrans(); + m_kart_position = btt.getOrigin(); + m_kart_rotation = btt.getRotation(); + } } // Camera //----------------------------------------------------------------------------- @@ -68,6 +73,8 @@ CameraNormal::CameraNormal(Camera::CameraType type, int camera_index, */ void CameraNormal::smoothMoveCamera(float dt) { + if(!m_kart) return; + Kart *kart = dynamic_cast(m_kart); if (kart->isFlying()) { From 2557a700cc9e9c05299234acb2240aad43c26cca Mon Sep 17 00:00:00 2001 From: Deve Date: Thu, 2 Feb 2017 22:26:37 +0100 Subject: [PATCH 027/378] Just to be more clear about explicit attrib location availability in GLES --- src/graphics/central_settings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index 53651fd36..d3ce43759 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -239,7 +239,8 @@ void CentralVideoSettings::init() hasTextureSwizzle = true; } - if (!GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_EXPLICIT_ATTRIB_LOCATION)) + if (!GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_EXPLICIT_ATTRIB_LOCATION) && + m_glsl == true) { Log::info("GLDriver", "Explicit Attrib Location Present"); hasExplicitAttribLocation = true; From 13081a07ec9ee9fb7eda62444ba3f39e9942436d Mon Sep 17 00:00:00 2001 From: Benau Date: Fri, 3 Feb 2017 08:41:18 +0800 Subject: [PATCH 028/378] Fix #2763 --- src/graphics/shader_based_renderer.cpp | 1 + src/graphics/shaders.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/shader_based_renderer.cpp b/src/graphics/shader_based_renderer.cpp index 99071fc83..c9352a9ba 100644 --- a/src/graphics/shader_based_renderer.cpp +++ b/src/graphics/shader_based_renderer.cpp @@ -944,6 +944,7 @@ void ShaderBasedRenderer::renderToTexture(GL3RenderTarget *render_target, // ---------------------------------------------------------------------------- void ShaderBasedRenderer::preloadShaderFiles() { + SharedGPUObjects::init(); ShaderFilesManager* sfm = ShaderFilesManager::getInstance(); sfm->addShaderFile("object_pass.vert", GL_VERTEX_SHADER); diff --git a/src/graphics/shaders.cpp b/src/graphics/shaders.cpp index 13befde7a..c563da4a3 100644 --- a/src/graphics/shaders.cpp +++ b/src/graphics/shaders.cpp @@ -259,7 +259,6 @@ void Shaders::loadShaders() } initGL(); - SharedGPUObjects::init(); } // loadShaders // ---------------------------------------------------------------------------- From 01765fce805beb271ae2eba6b410c9a211dbfcbc Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Thu, 2 Feb 2017 19:54:48 -0500 Subject: [PATCH 029/378] Fix crash --- src/tracks/track_object_presentation.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tracks/track_object_presentation.cpp b/src/tracks/track_object_presentation.cpp index 7824338d2..d47bf8119 100644 --- a/src/tracks/track_object_presentation.cpp +++ b/src/tracks/track_object_presentation.cpp @@ -178,6 +178,7 @@ TrackObjectPresentationLibraryNode::TrackObjectPresentationLibraryNode( ModelDefinitionLoader& model_def_loader) : TrackObjectPresentationSceneNode(xml_node) { + m_parent = NULL; m_start_executed = false; std::string name; @@ -288,13 +289,16 @@ void TrackObjectPresentationLibraryNode::update(float dt) m_start_executed = true; std::string fn_name = StringUtils::insertValues("void %s::onStart(const string)", m_name.c_str()); - std::string lib_id = m_parent->getID(); - std::string* lib_id_ptr = &lib_id; + if (m_parent != NULL) + { + std::string lib_id = m_parent->getID(); + std::string* lib_id_ptr = &lib_id; - Scripting::ScriptEngine::getInstance()->runFunction(false, fn_name, - [&](asIScriptContext* ctx) { - ctx->SetArgObject(0, lib_id_ptr); - }); + Scripting::ScriptEngine::getInstance()->runFunction(false, fn_name, + [&](asIScriptContext* ctx) { + ctx->SetArgObject(0, lib_id_ptr); + }); + } } } From 7c48c3e6cc71cd310853ce5adb7a0b5dec7df375 Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 3 Feb 2017 23:32:50 +0100 Subject: [PATCH 030/378] Choose a directory where we have the most available disk space for data extraction. It's not a perfect solution because at least on my device internal storage is much faster than sd card. But at least it should be much safer to choose a path with more free space. --- src/io/assets_android.cpp | 65 +++++++++++++++++++++++++++++++++++++++ src/io/assets_android.hpp | 1 + 2 files changed, 66 insertions(+) diff --git a/src/io/assets_android.cpp b/src/io/assets_android.cpp index 11f6f70fd..726c9d73d 100644 --- a/src/io/assets_android.cpp +++ b/src/io/assets_android.cpp @@ -27,6 +27,7 @@ #ifdef ANDROID #include +#include #endif //----------------------------------------------------------------------------- @@ -96,6 +97,25 @@ void AssetsAndroid::init() // Create data dir if it's not available anywhere if (m_stk_dir.size() == 0) + { + std::string preferred_path = getPreferredPath(paths); + + if (preferred_path.length() > 0) + { + if (m_file_manager->checkAndCreateDirectoryP(preferred_path + + "/stk/data")) + { + Log::info("AssetsAndroid", "Data directory created in: %s", + preferred_path.c_str()); + m_stk_dir = preferred_path + "/stk"; + needs_extract_data = true; + } + } + } + + // If getPreferredPath failed for some reason, then try to use the first + // available path + if (m_stk_dir.size() == 0) { for (std::string path : paths) { @@ -431,3 +451,48 @@ void AssetsAndroid::touchFile(std::string path) } //----------------------------------------------------------------------------- +/** Determines best path for extracting assets, depending on available disk + * space. + * \param paths A list of paths that should be checked + * \return Best path or empty string in case of error + */ +std::string AssetsAndroid::getPreferredPath(const std::vector& + paths) +{ +#ifdef ANDROID + std::string preferred_path; + int prev_available_space = 0; + + for (std::string path : paths) + { + // Paths that start with /data should be used only as a fallback if + // everything other doesn't work, because typical user doesn't have + // access to these directories and i.e. can't manually delete the files + // to clean up device + if (path.find("/data") == 0) + continue; + + struct statfs stat; + + if (statfs(path.c_str(), &stat) != 0) + continue; + + int available_space = (int)((stat.f_bavail * stat.f_bsize) / 1000000); + + Log::info("AssetsAndroid", "Available space in '%s': %i MB", + path.c_str(), available_space); + + if (available_space > prev_available_space) + { + preferred_path = path; + prev_available_space = available_space; + } + } + + return preferred_path; +#endif + + return ""; +} + +//----------------------------------------------------------------------------- diff --git a/src/io/assets_android.hpp b/src/io/assets_android.hpp index 0cccf566a..7ce164f97 100644 --- a/src/io/assets_android.hpp +++ b/src/io/assets_android.hpp @@ -32,6 +32,7 @@ private: bool extractDir(std::string dir_name); void removeData(); void touchFile(std::string path); + std::string getPreferredPath(const std::vector& paths); public: AssetsAndroid(FileManager* file_manager); From 6fae872a69e12660f2c0e0ea981fa4c142278a12 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 5 Feb 2017 09:11:48 +0800 Subject: [PATCH 031/378] Fix #2766 --- src/graphics/post_processing.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/graphics/post_processing.cpp b/src/graphics/post_processing.cpp index 89bd99503..923a26a09 100644 --- a/src/graphics/post_processing.cpp +++ b/src/graphics/post_processing.cpp @@ -354,16 +354,16 @@ public: } // render }; // BloomShader -static video::ITexture *lensDustTex = 0; - // ============================================================================ class BloomBlendShader : public TextureShader { +private: + video::ITexture* m_lens_dust_tex; public: BloomBlendShader() { - if (!lensDustTex) - lensDustTex = irr_driver->getTexture(FileManager::TEXTURE, "gfx_lensDust_a.png"); + m_lens_dust_tex = + irr_driver->getTexture(FileManager::TEXTURE, "gfx_lensDust_a.png"); loadProgram(OBJECT, GL_VERTEX_SHADER, "screenquad.vert", GL_FRAGMENT_SHADER, "bloomblend.frag"); @@ -381,7 +381,7 @@ public: setTextureUnits(render_target_bloom_128, render_target_bloom_256, render_target_bloom_512, - lensDustTex->getOpenGLTextureName()); + m_lens_dust_tex->getOpenGLTextureName()); drawFullScreenEffect(); } // render }; // BloomBlendShader From 5d30393641f1bb706e9c5310a7aab58420ad6ea4 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 5 Feb 2017 14:10:26 +0800 Subject: [PATCH 032/378] Allow reload texture with control+f3 for sam in game --- src/utils/debug.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 22753bfa0..714a21892 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -865,6 +865,11 @@ bool handleStaticAction(int key) { handleContextMenuAction(DEBUG_GUI_CAM_NORMAL); } + else if (key == KEY_F3) + { + STKTexManager::getInstance()->reloadTexture(""); + return true; + } // TODO: create more keyboard shortcuts return false; From 8858d4d86fd3bbcc294058bc3646e680573504a7 Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Mon, 6 Feb 2017 00:54:32 +0100 Subject: [PATCH 033/378] Parachute improvements (#2768) * Change grand-prix node for the new GP point system * New GP point system * Add parachute characteristics for rank and speed time multipliers * Add new parachute characteristics * typo fix * Add rank and speed parachutes characteristics * Add rank and speed parachute characteristics * Add rank and speed parachute characteristics * Add rank and speed parachute characteristics * Add rank and speed parachute characteristics * Add logic for parachute time scaling according to rank * Add logic for parachute time scaling according to speed * Separate GP and parachute branches * Separate GP and parachute branches --- data/kart_characteristics.xml | 15 ++++++++----- src/items/attachment.cpp | 15 +++++++++++++ src/items/powerup.cpp | 24 ++++++++++++++++---- src/karts/abstract_characteristic.cpp | 32 +++++++++++++++++++++++++++ src/karts/abstract_characteristic.hpp | 4 ++++ src/karts/kart_properties.cpp | 12 ++++++++++ src/karts/kart_properties.hpp | 2 ++ src/karts/xml_characteristic.cpp | 4 ++++ tools/create_kart_properties.py | 2 +- 9 files changed, 100 insertions(+), 10 deletions(-) diff --git a/data/kart_characteristics.xml b/data/kart_characteristics.xml index 9bc91c6f6..3fbd556d5 100644 --- a/data/kart_characteristics.xml +++ b/data/kart_characteristics.xml @@ -202,16 +202,21 @@ friction: The friction increase when a parachute is attached. duration: The time an attached parachute is active duration-other: The time a parachute attached from other kart works + duration-rank-mult: The multiplier applied to the duration of the + parachute on the 1st kart when affected by the item. Scale for + intermediary ranks to 1.0 for the last affected. + duration-speed-mult: Applied in all cases, multitplier to duration + of the parachute at max-speed. Scale to 1.0 at 0 speed. lbound-fraction: The lower bound fraction of speed when lost will - detach parachute. E.g. at nearly 0 speed, only 5% of speed + detach parachute. E.g. at nearly 0 speed, only 20% of speed need to be lost. ubound-fraction: The upper bound fraction of speed when lost will - detach parachute. E.g. at max-speed 30% of speed must be lost. + detach parachute. E.g. at max-speed 50% of speed must be lost. max-speed: A factor that decides the impact of rate of speed (distance between bounds) --> - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + Date: Sat, 11 Feb 2017 14:40:54 +0100 Subject: [PATCH 047/378] Update readme --- android/README.ANDROID | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/android/README.ANDROID b/android/README.ANDROID index 85903d41d..1a2f037b2 100644 --- a/android/README.ANDROID +++ b/android/README.ANDROID @@ -101,13 +101,7 @@ to just run: adding few lines to object pass shader, but we should rather try to find the real reason. -4. Explosion effect has poor performance and causes fps drop for a while. - Though it can be easily tweaked, so that less particles per second is - generated. - -5. Touch steering needs nice button icons. - -6. We use "exit(0)" at the end of main function. We shouldn't do it and we +4. We use "exit(0)" at the end of main function. We shouldn't do it and we should just return from the main function. But STK uses some global variables and their values are remembered when the game is restarted. We should properly clear them or re-initialize on startup. Using the "exit(0)" @@ -115,20 +109,18 @@ to just run: It seems to affect only Android 5.0. More information about the crash: https://code.google.com/p/android/issues/detail?id=160824 -7. STK crashes on Qualcomm with Adreno 305 when trying to draw menu interface. +5. STK crashes on Qualcomm with Adreno 305 when trying to draw menu interface. Backtrace shows glDrawElements function, and internally crashed in vbo allocation. -8. STK crashes on startup on some devices when aarch64 build is made using +6. STK crashes on startup on some devices when aarch64 build is made using Android r13 NDK. The r13 version has rather big modifications (it uses clang instead of gcc by default). This is probably a bug in NDK/compiler/OS, but for this reason using NDK r12 for 64-bit arm compilation is preferred. -9. Angelscript doesn't have full support for aarch64 builds, so that scripting +7. Angelscript doesn't have full support for aarch64 builds, so that scripting won't work on this platform. - -10. Steering with accelerometer is not available yet. It needs some work to do - it properly because tablets have different screen orientation than phones, - so that they receive events from different axies during rotating the device. - As far as I see it's not possible to read default screen orientation using - NDK functions. + +8. Turning left/right using accelerometer is available, but at this stage the + default screen orientation is not automatically detected and user must + manually choose if he needs "phone" or "tablet" accelerometer. From 68a99fd38b22d6555d24eb0d701870b8de0eb5f5 Mon Sep 17 00:00:00 2001 From: Deve Date: Sat, 11 Feb 2017 23:56:44 +0100 Subject: [PATCH 048/378] Fixed sRGB for GLES renderer. It's a bit ugly solution because we should handle it properly in one place and not add another sRGB correction... But it's already working solution and it doesn't affect the OpenGL renderer, so we can use it until better fix will be done. Now the GLES renderer looks almost the same as the original OpenGL 3.x one :) --- data/shaders/object_pass2.frag | 4 ++++ data/shaders/utils/getLightFactor.frag | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/data/shaders/object_pass2.frag b/data/shaders/object_pass2.frag index c7da60205..838570ead 100644 --- a/data/shaders/object_pass2.frag +++ b/data/shaders/object_pass2.frag @@ -40,7 +40,11 @@ void main(void) col = vec4(new_color.r, new_color.g, new_color.b, col.a); } +#ifdef GL_ES + col.xyz *= color.xyz; +#else col.xyz *= pow(color.xyz, vec3(2.2)); +#endif float specmap = texture(SpecMap, uv).g; float emitmap = texture(SpecMap, uv).b; FragColor = vec4(getLightFactor(col.xyz, vec3(1.), specmap, emitmap), 1.); diff --git a/data/shaders/utils/getLightFactor.frag b/data/shaders/utils/getLightFactor.frag index af0feed66..4fb754069 100644 --- a/data/shaders/utils/getLightFactor.frag +++ b/data/shaders/utils/getLightFactor.frag @@ -14,6 +14,10 @@ vec3 getLightFactor(vec3 diffuseMatColor, vec3 specularMatColor, float specMapVa vec3 DiffuseComponent = texture(DiffuseMap, tc).xyz; vec3 SpecularComponent = texture(SpecularMap, tc).xyz; float ao = texture(SSAO, tc).x; +#ifdef GL_ES + DiffuseComponent = pow(DiffuseComponent, vec3(1. / 2.2)); + SpecularComponent = pow(SpecularComponent, vec3(1. / 2.2)); +#endif vec3 tmp = diffuseMatColor * DiffuseComponent * (1. - specMapValue) + specularMatColor * SpecularComponent * specMapValue; vec3 emitCol = diffuseMatColor.xyz * diffuseMatColor.xyz * diffuseMatColor.xyz * 15.; return tmp * ao + (emitMapValue * emitCol); From 4af255431ce0206739707fcc72232b3b1f947642 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 12 Feb 2017 10:23:18 +0800 Subject: [PATCH 049/378] Fix opengl warning about glTexSubImage2D buffer of null pointer For example, whitespace in freetype glyph has no bits buffer --- src/font/font_with_face.cpp | 45 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/font/font_with_face.cpp b/src/font/font_with_face.cpp index 39426f74a..beaf33c30 100644 --- a/src/font/font_with_face.cpp +++ b/src/font/font_with_face.cpp @@ -206,28 +206,33 @@ void FontWithFace::insertGlyph(wchar_t c, const GlyphInfo& gi) const unsigned int cur_tex = m_spritebank->getTextureCount() -1; #ifndef SERVER_ONLY - video::ITexture* tex = m_spritebank->getTexture(cur_tex); - glBindTexture(GL_TEXTURE_2D, tex->getOpenGLTextureName()); - assert(bits->pixel_mode == FT_PIXEL_MODE_GRAY); - if (CVS->isARBTextureSwizzleUsable()) + if (bits->buffer != NULL) { - glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height, - bits->width, bits->rows, GL_RED, GL_UNSIGNED_BYTE, bits->buffer); + video::ITexture* tex = m_spritebank->getTexture(cur_tex); + glBindTexture(GL_TEXTURE_2D, tex->getOpenGLTextureName()); + assert(bits->pixel_mode == FT_PIXEL_MODE_GRAY); + if (CVS->isARBTextureSwizzleUsable()) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height, + bits->width, bits->rows, GL_RED, GL_UNSIGNED_BYTE, + bits->buffer); + } + else + { + const unsigned int size = bits->width * bits->rows; + uint8_t* image_data = new uint8_t[size * 4]; + memset(image_data, 255, size * 4); + for (unsigned int i = 0; i < size; i++) + image_data[4 * i + 3] = bits->buffer[i]; + glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height, + bits->width, bits->rows, GL_BGRA, GL_UNSIGNED_BYTE, + image_data); + delete[] image_data; + } + if (tex->hasMipMaps()) + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); } - else - { - const unsigned int size = bits->width * bits->rows; - uint8_t* image_data = new uint8_t[size * 4]; - memset(image_data, 255, size * 4); - for (unsigned int i = 0; i < size; i++) - image_data[4 * i + 3] = bits->buffer[i]; - glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height, - bits->width, bits->rows, GL_BGRA, GL_UNSIGNED_BYTE, image_data); - delete[] image_data; - } - if (tex->hasMipMaps()) - glGenerateMipmap(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); #endif // Store the rectangle of current glyph From ca0a605b0ec02e91fc9c030538b8b9948c3631ad Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 12 Feb 2017 15:42:22 +0800 Subject: [PATCH 050/378] Don't use texture buffer object if not supported, see #2571 Also allow to use setTextureUnits for texture buffer --- src/graphics/central_settings.cpp | 11 ++++++++++ src/graphics/central_settings.hpp | 2 ++ src/graphics/gpu_particles.cpp | 19 ++++++++--------- src/graphics/particle_emitter.cpp | 2 +- src/graphics/skybox.cpp | 35 +++++++++++++------------------ src/graphics/texture_shader.cpp | 13 ++++++++++-- src/graphics/texture_shader.hpp | 7 +++++-- 7 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index d3ce43759..8bcd24b67 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -52,6 +52,7 @@ void CentralVideoSettings::init() hasGS = false; hasTextureFilterAnisotropic = false; hasTextureSwizzle = false; + hasTextureBufferObject = false; #if defined(USE_GLES2) hasBGRA = false; @@ -196,6 +197,11 @@ void CentralVideoSettings::init() hasTextureSwizzle = true; Log::info("GLDriver", "ARB Texture Swizzle Present"); } + if (hasGLExtension("GL_ARB_texture_buffer_object")) + { + hasTextureBufferObject = true; + Log::info("GLDriver", "ARB Texture Buffer Object Present"); + } // Only unset the high def textures if they are set as default. If the // user has enabled them (bit 1 set), then leave them enabled. if (GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_HIGHDEFINITION_TEXTURES) && @@ -476,4 +482,9 @@ bool CentralVideoSettings::isARBTextureSwizzleUsable() const return m_glsl && hasTextureSwizzle; } +bool CentralVideoSettings::isARBTextureBufferObjectUsable() const +{ + return hasTextureBufferObject; +} + #endif // !SERVER_ONLY diff --git a/src/graphics/central_settings.hpp b/src/graphics/central_settings.hpp index 146160e0b..e14ed8671 100644 --- a/src/graphics/central_settings.hpp +++ b/src/graphics/central_settings.hpp @@ -44,6 +44,7 @@ private: bool hasMultiDrawIndirect; bool hasTextureFilterAnisotropic; bool hasTextureSwizzle; + bool hasTextureBufferObject; #if defined(USE_GLES2) bool hasBGRA; @@ -84,6 +85,7 @@ public: bool isARBExplicitAttribLocationUsable() const; bool isEXTTextureFilterAnisotropicUsable() const; bool isARBTextureSwizzleUsable() const; + bool isARBTextureBufferObjectUsable() const; #if defined(USE_GLES2) bool isEXTTextureFormatBGRA8888Usable() const; diff --git a/src/graphics/gpu_particles.cpp b/src/graphics/gpu_particles.cpp index f1dacb5f3..ac2fecb60 100644 --- a/src/graphics/gpu_particles.cpp +++ b/src/graphics/gpu_particles.cpp @@ -91,13 +91,13 @@ public: // ============================================================================ /** */ -class HeightmapSimulationShader : public Shader +class HeightmapSimulationShader : + public TextureShader { public: - GLuint m_TU_heightmap; - HeightmapSimulationShader() { const char *varyings[] = {"new_particle_position", "new_lifetime", @@ -105,8 +105,7 @@ public: loadTFBProgram("particlesimheightmap.vert", varyings, 4); assignUniforms("sourcematrix", "dt", "level", "size_increase_factor", "track_x", "track_x_len", "track_z", "track_z_len"); - m_TU_heightmap = 2; - assignTextureUnit(m_TU_heightmap, "heightmap"); + assignSamplerNames(0, "heightmap", ST_TEXTURE_BUFFER); } // HeightmapSimulationShader @@ -203,6 +202,7 @@ void ParticleSystemProxy::setHeightmap(const std::vector > &h glBindTexture(GL_TEXTURE_BUFFER, heightmaptexture); glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, heighmapbuffer); glBindBuffer(GL_TEXTURE_BUFFER, 0); + glBindTexture(GL_TEXTURE_BUFFER, 0); delete[] hm_array; #endif @@ -475,10 +475,9 @@ void ParticleSystemProxy::simulate() { #if !defined(USE_GLES2) HeightmapSimulationShader::getInstance()->use(); - glActiveTexture(GL_TEXTURE0 + HeightmapSimulationShader::getInstance()->m_TU_heightmap); - glBindTexture(GL_TEXTURE_BUFFER, heightmaptexture); + HeightmapSimulationShader::getInstance()->setTextureUnits(heightmaptexture); HeightmapSimulationShader::getInstance()->setUniforms(matrix, timediff, active_count, size_increase_factor, track_x, track_x_len, track_z, track_z_len); -#endif +#endif } else { diff --git a/src/graphics/particle_emitter.cpp b/src/graphics/particle_emitter.cpp index 8150d0cde..447c4935a 100644 --- a/src/graphics/particle_emitter.cpp +++ b/src/graphics/particle_emitter.cpp @@ -710,7 +710,7 @@ void ParticleEmitter::setParticleType(const ParticleKind* type) void ParticleEmitter::addHeightMapAffector(Track* t) { - if (m_is_glsl) + if (m_is_glsl && CVS->isARBTextureBufferObjectUsable()) { const Vec3* aabb_min; const Vec3* aabb_max; diff --git a/src/graphics/skybox.cpp b/src/graphics/skybox.cpp index 51bc63ea7..507a0cf79 100644 --- a/src/graphics/skybox.cpp +++ b/src/graphics/skybox.cpp @@ -57,21 +57,18 @@ public: }; // SkyboxShader -class SpecularIBLGenerator : public TextureShader { public: - GLuint m_tu_samples; SpecularIBLGenerator() { loadProgram(OBJECT, GL_VERTEX_SHADER, "screenquad.vert", GL_FRAGMENT_SHADER, "importance_sampling_specular.frag"); assignUniforms("PermutationMatrix", "ViewportSize"); - m_tu_samples = 1; - assignSamplerNames(0, "tex", ST_TRILINEAR_CUBEMAP); - assignTextureUnit(m_tu_samples, "samples"); + assignSamplerNames(0, "tex", ST_TRILINEAR_CUBEMAP, + 1, "samples", ST_TEXTURE_BUFFER); } - }; // SpecularIBLGenerator @@ -231,7 +228,7 @@ void Skybox::generateSpecularCubemap() } glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - if (!CVS->isDefferedEnabled()) + if (!CVS->isDefferedEnabled() || !CVS->isARBTextureBufferObjectUsable()) return; #if !defined(USE_GLES2) @@ -277,16 +274,15 @@ void Skybox::generateSpecularCubemap() } glBindVertexArray(0); - glActiveTexture(GL_TEXTURE0 + - SpecularIBLGenerator::getInstance()->m_tu_samples); - GLuint sampleTex, sampleBuffer; - glGenBuffers(1, &sampleBuffer); - glBindBuffer(GL_TEXTURE_BUFFER, sampleBuffer); + GLuint sample_texture, sample_buffer; + glGenBuffers(1, &sample_buffer); + glBindBuffer(GL_TEXTURE_BUFFER, sample_buffer); glBufferData(GL_TEXTURE_BUFFER, 2048 * sizeof(float), tmp, GL_STATIC_DRAW); - glGenTextures(1, &sampleTex); - glBindTexture(GL_TEXTURE_BUFFER, sampleTex); - glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, sampleBuffer); + glGenTextures(1, &sample_texture); + glBindTexture(GL_TEXTURE_BUFFER, sample_texture); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, sample_buffer); + glBindTexture(GL_TEXTURE_BUFFER, 0); glBindVertexArray(SharedGPUObjects::getFullScreenQuadVAO()); for (unsigned face = 0; face < 6; face++) @@ -298,20 +294,19 @@ void Skybox::generateSpecularCubemap() GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER); assert(status == GL_FRAMEBUFFER_COMPLETE); - SpecularIBLGenerator::getInstance()->setTextureUnits(m_cube_map); + SpecularIBLGenerator::getInstance() + ->setTextureUnits(m_cube_map, sample_texture); SpecularIBLGenerator::getInstance()->setUniforms(M[face], viewportSize); glDrawArrays(GL_TRIANGLES, 0, 3); } - glActiveTexture( GL_TEXTURE0 - + SpecularIBLGenerator::getInstance()->m_tu_samples); glBindBuffer(GL_TEXTURE_BUFFER, 0); glBindTexture(GL_TEXTURE_BUFFER, 0); delete[] tmp; - glDeleteTextures(1, &sampleTex); - glDeleteBuffers(1, &sampleBuffer); + glDeleteTextures(1, &sample_texture); + glDeleteBuffers(1, &sample_buffer); } glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); diff --git a/src/graphics/texture_shader.cpp b/src/graphics/texture_shader.cpp index 48c11e17f..5cdeda0bf 100644 --- a/src/graphics/texture_shader.cpp +++ b/src/graphics/texture_shader.cpp @@ -33,7 +33,8 @@ TextureShaderBase::BindFunction TextureShaderBase::m_all_bind_functions[] = /* ST_VOLUME_LINEAR_FILTERED */ &TextureShaderBase::bindTextureVolume, /* ST_NEARED_CLAMPED_FILTERED */ &TextureShaderBase::bindTextureNearestClamped, /* ST_BILINEAR_CLAMPED_FILTERED */ &TextureShaderBase::bindTextureBilinearClamped, - /* ST_SEMI_TRILINEAR */ &TextureShaderBase::bindTextureSemiTrilinear + /* ST_SEMI_TRILINEAR */ &TextureShaderBase::bindTextureSemiTrilinear, + /* ST_TEXTURE_BUFFER */ &TextureShaderBase::bindTextureBuffer }; GLuint TextureShaderBase::m_all_texture_types[] = @@ -46,7 +47,8 @@ GLuint TextureShaderBase::m_all_texture_types[] = /* ST_VOLUME_LINEAR_FILTERED */ GL_TEXTURE_3D, /* ST_NEARED_CLAMPED_FILTERED */ GL_TEXTURE_2D, /* ST_BILINEAR_CLAMPED_FILTERED */ GL_TEXTURE_2D, - /* ST_SEMI_TRILINEAR */ GL_TEXTURE_2D + /* ST_SEMI_TRILINEAR */ GL_TEXTURE_2D, + /* ST_TEXTURE_BUFFER */ GL_TEXTURE_BUFFER }; // ---------------------------------------------------------------------------- @@ -116,6 +118,11 @@ void TextureShaderBase::bindTextureNearestClamped(GLuint texture_unit, } // bindTextureNearestClamped // ---------------------------------------------------------------------------- +void TextureShaderBase::bindTextureBuffer(GLuint texture_unit, GLuint tex_id) +{ + glActiveTexture(GL_TEXTURE0 + texture_unit); + glBindTexture(GL_TEXTURE_BUFFER, tex_id); +} // bindTextureBuffer // ---------------------------------------------------------------------------- void TextureShaderBase::bindTextureBilinear(GLuint texture_unit, GLuint tex) @@ -239,6 +246,8 @@ GLuint TextureShaderBase::createSamplers(SamplerTypeNew sampler_type) return createBilinearClampedSampler(); case ST_SEMI_TRILINEAR: return createSemiTrilinearSampler(); + case ST_TEXTURE_BUFFER: + return 0; default: assert(false); return 0; diff --git a/src/graphics/texture_shader.hpp b/src/graphics/texture_shader.hpp index 244c78b7e..3a2af2600 100644 --- a/src/graphics/texture_shader.hpp +++ b/src/graphics/texture_shader.hpp @@ -43,7 +43,8 @@ enum SamplerTypeNew ST_NEARED_CLAMPED_FILTERED, ST_BILINEAR_CLAMPED_FILTERED, ST_SEMI_TRILINEAR, - ST_MAX = ST_SEMI_TRILINEAR + ST_TEXTURE_BUFFER, + ST_MAX = ST_TEXTURE_BUFFER }; // SamplerTypeNew // ============================================================================ @@ -68,6 +69,7 @@ protected: static void bindTextureShadow(GLuint tex_unit, GLuint tex_id); static void bindTrilinearClampedArrayTexture(GLuint tex_unit, GLuint tex_id); static void bindTextureVolume(GLuint tex_unit, GLuint tex_id); + static void bindTextureBuffer(GLuint tex_unit, GLuint tex_id); GLuint createSamplers(SamplerTypeNew sampler_type); private: @@ -179,7 +181,8 @@ public: { glActiveTexture(GL_TEXTURE0 + m_texture_units[N]); glBindTexture(m_texture_type[N], tex_id); - glBindSampler(m_texture_units[N], m_sampler_ids[N]); + if (m_sampler_ids[N] != 0) + glBindSampler(m_texture_units[N], m_sampler_ids[N]); } else { From d3cbc6ca0e6c4144e30685234569cbc3da2832b9 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 13 Feb 2017 00:45:18 +0800 Subject: [PATCH 051/378] Fix GLES --- src/graphics/gpu_particles.cpp | 2 ++ src/graphics/skybox.cpp | 4 ++-- src/graphics/texture_shader.cpp | 11 +++++++++-- src/graphics/texture_shader.hpp | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/graphics/gpu_particles.cpp b/src/graphics/gpu_particles.cpp index ac2fecb60..1b7f5da39 100644 --- a/src/graphics/gpu_particles.cpp +++ b/src/graphics/gpu_particles.cpp @@ -91,6 +91,7 @@ public: // ============================================================================ /** */ +#if !defined(USE_GLES2) class HeightmapSimulationShader : public TextureShader { @@ -70,7 +70,7 @@ public: 1, "samples", ST_TEXTURE_BUFFER); } }; // SpecularIBLGenerator - +#endif namespace { // ---------------------------------------------------------------------------- diff --git a/src/graphics/texture_shader.cpp b/src/graphics/texture_shader.cpp index 5cdeda0bf..a6026a0d0 100644 --- a/src/graphics/texture_shader.cpp +++ b/src/graphics/texture_shader.cpp @@ -47,8 +47,11 @@ GLuint TextureShaderBase::m_all_texture_types[] = /* ST_VOLUME_LINEAR_FILTERED */ GL_TEXTURE_3D, /* ST_NEARED_CLAMPED_FILTERED */ GL_TEXTURE_2D, /* ST_BILINEAR_CLAMPED_FILTERED */ GL_TEXTURE_2D, - /* ST_SEMI_TRILINEAR */ GL_TEXTURE_2D, - /* ST_TEXTURE_BUFFER */ GL_TEXTURE_BUFFER + /* ST_SEMI_TRILINEAR */ GL_TEXTURE_2D +#ifndef USE_GLES2 + /* ST_TEXTURE_BUFFER */, GL_TEXTURE_BUFFER +#endif + }; // ---------------------------------------------------------------------------- @@ -120,8 +123,10 @@ void TextureShaderBase::bindTextureNearestClamped(GLuint texture_unit, // ---------------------------------------------------------------------------- void TextureShaderBase::bindTextureBuffer(GLuint texture_unit, GLuint tex_id) { +#ifndef USE_GLES2 glActiveTexture(GL_TEXTURE0 + texture_unit); glBindTexture(GL_TEXTURE_BUFFER, tex_id); +#endif } // bindTextureBuffer // ---------------------------------------------------------------------------- @@ -246,8 +251,10 @@ GLuint TextureShaderBase::createSamplers(SamplerTypeNew sampler_type) return createBilinearClampedSampler(); case ST_SEMI_TRILINEAR: return createSemiTrilinearSampler(); +#ifndef USE_GLES2 case ST_TEXTURE_BUFFER: return 0; +#endif default: assert(false); return 0; diff --git a/src/graphics/texture_shader.hpp b/src/graphics/texture_shader.hpp index 3a2af2600..f49b93c08 100644 --- a/src/graphics/texture_shader.hpp +++ b/src/graphics/texture_shader.hpp @@ -43,8 +43,12 @@ enum SamplerTypeNew ST_NEARED_CLAMPED_FILTERED, ST_BILINEAR_CLAMPED_FILTERED, ST_SEMI_TRILINEAR, +#ifdef USE_GLES2 + ST_MAX = ST_SEMI_TRILINEAR +#else ST_TEXTURE_BUFFER, ST_MAX = ST_TEXTURE_BUFFER +#endif }; // SamplerTypeNew // ============================================================================ From c635e8d1c45a2951c2f556cc6e48b4684585e323 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 13 Feb 2017 10:06:35 +0800 Subject: [PATCH 052/378] Get rid of some srgb conversion in shader, see #2787 --- data/shaders/detailed_object_pass2.frag | 1 - .../instanced_detailed_object_pass2.frag | 1 - src/graphics/stk_mesh_loader.cpp | 21 ++++--------------- src/graphics/stk_mesh_loader.hpp | 19 +++-------------- 4 files changed, 7 insertions(+), 35 deletions(-) diff --git a/data/shaders/detailed_object_pass2.frag b/data/shaders/detailed_object_pass2.frag index 4c02b86f9..03638a870 100644 --- a/data/shaders/detailed_object_pass2.frag +++ b/data/shaders/detailed_object_pass2.frag @@ -23,7 +23,6 @@ void main(void) #endif #endif vec4 detail = texture(Detail, uv_bis); - detail.xyz = pow(detail.xyz, vec3(2.2)); detail.rgb = detail.a * detail.rgb; color.rgb = detail.rgb + color.rgb * (1. - detail.a); float specmap = texture(SpecMap, uv).g; diff --git a/data/shaders/instanced_detailed_object_pass2.frag b/data/shaders/instanced_detailed_object_pass2.frag index ffadbaab7..e3e13e74b 100644 --- a/data/shaders/instanced_detailed_object_pass2.frag +++ b/data/shaders/instanced_detailed_object_pass2.frag @@ -29,7 +29,6 @@ void main(void) vec4 detail = texture(Detail, uv_bis); float specmap = texture(SpecMap, uv).g; #endif - detail.xyz = pow(detail.xyz, vec3(2.2)); detail.rgb = detail.a * detail.rgb; color.rgb = detail.rgb + color.rgb * (1. - detail.a); FragColor = vec4(getLightFactor(color.xyz, vec3(1.), specmap, 0.), 1.); diff --git a/src/graphics/stk_mesh_loader.cpp b/src/graphics/stk_mesh_loader.cpp index c1a7fc892..b2918b61a 100644 --- a/src/graphics/stk_mesh_loader.cpp +++ b/src/graphics/stk_mesh_loader.cpp @@ -1,19 +1,6 @@ -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2017 SuperTuxKart-Team -// -// 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, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h #include "graphics/stk_mesh_loader.hpp" #include "graphics/stk_tex_manager.hpp" @@ -1059,7 +1046,7 @@ void STKMeshLoader::loadTextures(SB3dMaterial& material) const video::ITexture* tex = STKTexManager::getInstance()->getTexture(full_path.c_str(), - i == 0 ? true : false/*is_srgb*/, false/*premul_alpha*/, + i <= 1 ? true : false/*is_srgb*/, false/*premul_alpha*/, true/*set_material*/); material.Material.setTexture(i, tex); diff --git a/src/graphics/stk_mesh_loader.hpp b/src/graphics/stk_mesh_loader.hpp index 41b9fdc18..9319acb53 100644 --- a/src/graphics/stk_mesh_loader.hpp +++ b/src/graphics/stk_mesh_loader.hpp @@ -1,19 +1,6 @@ -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2017 SuperTuxKart-Team -// -// 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, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h #ifndef HEADER_STK_MESH_LOADER_HPP #define HEADER_STK_MESH_LOADER_HPP From f181acea3da9d5b1632e9724d585a30e81572461 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Wed, 15 Feb 2017 18:38:56 -0500 Subject: [PATCH 053/378] Add Jymis to credits --- data/CREDITS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/CREDITS b/data/CREDITS index 96f2b617d..746f9dcdd 100644 --- a/data/CREDITS +++ b/data/CREDITS @@ -119,6 +119,9 @@ Karts Objects - GeekPenguinBR, TuxKartDriver : Models from Las Dunas Stadium +Jymis +- Karts and icons + Minibjorn - Powerups and baddies (models + icons) - Explosion and smoke particles From 251c46dcc443a0a8199ee07b25045435b88d657d Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 17 Feb 2017 01:03:08 +0100 Subject: [PATCH 054/378] Always call bind sampler function. This fixes errors for Intel HD4000 on Windows. My understending of this issue is following: - a shader assigned two textures, i.e. texture1 = sampler1, texture2=sampler2 - then next shader (i.e. importance sampling specular) assigned two textures: texture1=sampler1, texture2=without_sampler - when we executed setTextureUnits, the glBindSampler function wasn't called at all, so the drivers were trying to use combination texture2=sampler2, even though the sampler2 was prepared for different texture and most likely was already deleted. We can just always call the glBindSampler function, so that if texture doesn't have a sampler, it will execute it with sampler_id=0 param, which is practically an "unbind" function. --- src/graphics/texture_shader.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/graphics/texture_shader.hpp b/src/graphics/texture_shader.hpp index f49b93c08..1fc52555b 100644 --- a/src/graphics/texture_shader.hpp +++ b/src/graphics/texture_shader.hpp @@ -185,8 +185,7 @@ public: { glActiveTexture(GL_TEXTURE0 + m_texture_units[N]); glBindTexture(m_texture_type[N], tex_id); - if (m_sampler_ids[N] != 0) - glBindSampler(m_texture_units[N], m_sampler_ids[N]); + glBindSampler(m_texture_units[N], m_sampler_ids[N]); } else { From 299a4dc5f29840dfc551014bfd793a23fc08fa1e Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 17 Feb 2017 06:31:15 +0100 Subject: [PATCH 055/378] Don't check the TBO extension --- src/graphics/central_settings.cpp | 11 ----------- src/graphics/central_settings.hpp | 2 -- src/graphics/particle_emitter.cpp | 3 +-- src/graphics/skybox.cpp | 2 +- 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/graphics/central_settings.cpp b/src/graphics/central_settings.cpp index 8bcd24b67..d3ce43759 100644 --- a/src/graphics/central_settings.cpp +++ b/src/graphics/central_settings.cpp @@ -52,7 +52,6 @@ void CentralVideoSettings::init() hasGS = false; hasTextureFilterAnisotropic = false; hasTextureSwizzle = false; - hasTextureBufferObject = false; #if defined(USE_GLES2) hasBGRA = false; @@ -197,11 +196,6 @@ void CentralVideoSettings::init() hasTextureSwizzle = true; Log::info("GLDriver", "ARB Texture Swizzle Present"); } - if (hasGLExtension("GL_ARB_texture_buffer_object")) - { - hasTextureBufferObject = true; - Log::info("GLDriver", "ARB Texture Buffer Object Present"); - } // Only unset the high def textures if they are set as default. If the // user has enabled them (bit 1 set), then leave them enabled. if (GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_HIGHDEFINITION_TEXTURES) && @@ -482,9 +476,4 @@ bool CentralVideoSettings::isARBTextureSwizzleUsable() const return m_glsl && hasTextureSwizzle; } -bool CentralVideoSettings::isARBTextureBufferObjectUsable() const -{ - return hasTextureBufferObject; -} - #endif // !SERVER_ONLY diff --git a/src/graphics/central_settings.hpp b/src/graphics/central_settings.hpp index e14ed8671..146160e0b 100644 --- a/src/graphics/central_settings.hpp +++ b/src/graphics/central_settings.hpp @@ -44,7 +44,6 @@ private: bool hasMultiDrawIndirect; bool hasTextureFilterAnisotropic; bool hasTextureSwizzle; - bool hasTextureBufferObject; #if defined(USE_GLES2) bool hasBGRA; @@ -85,7 +84,6 @@ public: bool isARBExplicitAttribLocationUsable() const; bool isEXTTextureFilterAnisotropicUsable() const; bool isARBTextureSwizzleUsable() const; - bool isARBTextureBufferObjectUsable() const; #if defined(USE_GLES2) bool isEXTTextureFormatBGRA8888Usable() const; diff --git a/src/graphics/particle_emitter.cpp b/src/graphics/particle_emitter.cpp index 447c4935a..b6dbb059b 100644 --- a/src/graphics/particle_emitter.cpp +++ b/src/graphics/particle_emitter.cpp @@ -709,8 +709,7 @@ void ParticleEmitter::setParticleType(const ParticleKind* type) void ParticleEmitter::addHeightMapAffector(Track* t) { - - if (m_is_glsl && CVS->isARBTextureBufferObjectUsable()) + if (m_is_glsl) { const Vec3* aabb_min; const Vec3* aabb_max; diff --git a/src/graphics/skybox.cpp b/src/graphics/skybox.cpp index 6bb25b54e..9c7a27a48 100644 --- a/src/graphics/skybox.cpp +++ b/src/graphics/skybox.cpp @@ -228,7 +228,7 @@ void Skybox::generateSpecularCubemap() } glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - if (!CVS->isDefferedEnabled() || !CVS->isARBTextureBufferObjectUsable()) + if (!CVS->isDefferedEnabled()) return; #if !defined(USE_GLES2) From 269b1b5cb93dafb470a41113b284d14b75746861 Mon Sep 17 00:00:00 2001 From: Benau Date: Fri, 17 Feb 2017 13:52:50 +0800 Subject: [PATCH 056/378] Add restriction for intel hd4600 on windows about compute shader Fix #1916 --- data/graphical_restrictions.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/graphical_restrictions.xml b/data/graphical_restrictions.xml index 48d47dfeb..d91be52a9 100644 --- a/data/graphical_restrictions.xml +++ b/data/graphical_restrictions.xml @@ -3,6 +3,7 @@ + From ab8a68f295b479653c1c5b033e85a8515e9ab2c4 Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 17 Feb 2017 19:44:28 +0100 Subject: [PATCH 057/378] Enable HD textures on newer intel graphics cards. It should work fine for intel HD4000 and newer, especially after recent memory management improvements in STK. --- data/graphical_restrictions.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/graphical_restrictions.xml b/data/graphical_restrictions.xml index d91be52a9..3623371cf 100644 --- a/data/graphical_restrictions.xml +++ b/data/graphical_restrictions.xml @@ -3,8 +3,10 @@ + + @@ -12,7 +14,6 @@ - From 5cdcf39583e428a1f1e3ea58e76ac1a65844f68c Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 17 Feb 2017 19:50:50 +0100 Subject: [PATCH 058/378] Add intel HD2000 to graphics restrictions. It's just slower HD3000 version, so disable the same features for it. --- data/graphical_restrictions.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/graphical_restrictions.xml b/data/graphical_restrictions.xml index 3623371cf..a9d9bd657 100644 --- a/data/graphical_restrictions.xml +++ b/data/graphical_restrictions.xml @@ -1,12 +1,16 @@ + + + + + + - - From 40e326bb4145017ee946915c16485dca711ddcb3 Mon Sep 17 00:00:00 2001 From: Deve Date: Fri, 17 Feb 2017 21:23:22 +0100 Subject: [PATCH 059/378] Avoid using fixed pipeline on newer intel graphics cards. The force legacy device graphics restriction is needed for ironlake graphics cards. But newer generations in pentium/celeron processors also are named just "Intel(R) HD Graphics" without any number. The newest drivers for ironlake graphics card is 8.15 and it's unlikely that it will get an update. So assume that all drivers older than 9.x are ironlake generation or that are too old to run STK anyway. --- data/graphical_restrictions.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/graphical_restrictions.xml b/data/graphical_restrictions.xml index a9d9bd657..db099d8df 100644 --- a/data/graphical_restrictions.xml +++ b/data/graphical_restrictions.xml @@ -1,7 +1,7 @@ - - + + From 70511c53487f2f80604e511529a197dd08c03e00 Mon Sep 17 00:00:00 2001 From: Arthur-D Date: Fri, 17 Feb 2017 23:13:39 +0100 Subject: [PATCH 060/378] Lower terrain-impulse. It should keep its intended function of avoiding getting stuck when driving perpendicular to walls, but should be less forceful and look less artificial as a result. --- data/stk_config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/stk_config.xml b/data/stk_config.xml index d4f4b8a9f..b782a1a69 100644 --- a/data/stk_config.xml +++ b/data/stk_config.xml @@ -392,7 +392,7 @@ outside of the chassis and results in more stable physical behaviour of the karts. --> From 131dc4bfad514f4751f1a5822117d9edb3e1fa9b Mon Sep 17 00:00:00 2001 From: Benau Date: Sat, 18 Feb 2017 14:30:35 +0800 Subject: [PATCH 061/378] Fix #2786 --- src/replay/replay_recorder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/replay/replay_recorder.cpp b/src/replay/replay_recorder.cpp index 3557ea97a..4a1c986e2 100644 --- a/src/replay/replay_recorder.cpp +++ b/src/replay/replay_recorder.cpp @@ -193,7 +193,7 @@ void ReplayRecorder::save() for (unsigned int k = 0; k < num_karts; k++) { if (world->getKart(k)->isGhostKart()) continue; - float cur_time = m_transform_events[k][m_count_transforms[k]-1].m_time; + float cur_time = world->getKart(k)->getFinishTime(); if (cur_time < min_time) min_time = cur_time; } From 30cff0075783655208a5d573a67d749cd9596835 Mon Sep 17 00:00:00 2001 From: hiker Date: Wed, 15 Feb 2017 23:56:43 +1100 Subject: [PATCH 062/378] Reduce internal physics timestep to test if collision handling improves. --- src/physics/physics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/physics/physics.cpp b/src/physics/physics.cpp index bd3e92f55..06926a3cd 100644 --- a/src/physics/physics.cpp +++ b/src/physics/physics.cpp @@ -152,7 +152,7 @@ void Physics::update(float dt) // Maximum of three substeps. This will work for framerate down to // 20 FPS (bullet default frequency is 60 HZ). - m_dynamics_world->stepSimulation(dt, 3); + m_dynamics_world->stepSimulation(dt, 6, 1.0f/120.0f); // Now handle the actual collision. Note: flyables can not be removed // inside of this loop, since the same flyables might hit more than one From a17b537699a1ff65255a6b4e9e28741c6101c97d Mon Sep 17 00:00:00 2001 From: hiker Date: Mon, 20 Feb 2017 07:41:33 +1100 Subject: [PATCH 063/378] Make cannon smoother. --- src/animations/ipo.cpp | 48 ++++++++++++++++++++++++++++++++++ src/animations/ipo.hpp | 11 ++++---- src/karts/cannon_animation.cpp | 23 ++++++++++------ src/karts/cannon_animation.hpp | 19 ++++++++++---- src/tracks/check_cannon.cpp | 11 ++++---- src/tracks/check_cannon.hpp | 3 ++- src/tracks/check_line.hpp | 4 +++ 7 files changed, 95 insertions(+), 24 deletions(-) diff --git a/src/animations/ipo.cpp b/src/animations/ipo.cpp index 4499592af..139378710 100644 --- a/src/animations/ipo.cpp +++ b/src/animations/ipo.cpp @@ -363,6 +363,38 @@ float Ipo::IpoData::getCubicBezier(float t, float p0, float p1, return ((a*t+b)*t+c)*t+p0; } // bezier +// ---------------------------------------------------------------------------- +/** Determines the rotation between the start and end of this curve. + */ +btQuaternion Ipo::IpoData::getOverallRotation() +{ + // Vectors at start and end of curve + Vec3 start, end; + + if (m_interpolation == IP_BEZIER) + { + // In case of Bezier use the handles to get initial and final + // orientation + start = m_handle2[0] - m_handle1[0]; + end = *m_handle2.back() - *m_handle1.back(); + } + else // Const or linear + { + // In this case determine the start vector by selecting using the + // beginning and a second point a bit further on the curve + start.setX(get(m_start_time + 0.1f, 0, 0) - m_points[0].getX()); + start.setY(get(m_start_time + 0.1f, 1, 0) - m_points[0].getY()); + start.setZ(get(m_start_time + 0.1f, 2, 0) - m_points[0].getZ()); + int n = m_points.size() - 2; + end. setX(get(m_end_time - 0.1f, 0, n) - m_points[n].getX()); + end. setY(get(m_end_time - 0.1f, 1, n) - m_points[n].getY()); + end. setZ(get(m_end_time - 0.1f, 2, n) - m_points[n].getZ()); + } + + btQuaternion q = shortestArcQuatNormalize2(start, end); + return q; +} // IpData::getOverallRoation + // ============================================================================ /** The Ipo constructor. Ipos can share the actual data to interpolate, which * is stored in a separate IpoData object, see Ipo(const Ipo *ipo) @@ -499,3 +531,19 @@ float Ipo::get(float time, unsigned int index) const assert(!std::isnan(rval)); return rval; } // get + +// ---------------------------------------------------------------------------- +/** Return the quaternion that rotates an object form the start of the IPO + * to the end. + */ +btQuaternion Ipo::getOverallRotation() +{ + // In case of a single point only: + if (m_next_n == 0) + { + // Return a unit quaternion + btQuaternion q(0, 0, 0, 1); + return q; + } + return m_ipo_data->getOverallRotation(); +} // getOverallRoation \ No newline at end of file diff --git a/src/animations/ipo.hpp b/src/animations/ipo.hpp index fadff2028..39eda9816 100644 --- a/src/animations/ipo.hpp +++ b/src/animations/ipo.hpp @@ -81,10 +81,10 @@ private: private: float getCubicBezier(float t, float p0, float p1, float p2, float p3) const; - void approximateBezier(float t0, float t1, - const Vec3 &p0, const Vec3 &p1, - const Vec3 &h0, const Vec3 &h2, - unsigned int rec_level = 0); + void approximateBezier(float t0, float t1, + const Vec3 &p0, const Vec3 &p1, + const Vec3 &h0, const Vec3 &h2, + unsigned int rec_level = 0); public: IpoData(const XMLNode &curve, float fps, bool reverse); void readCurve(const XMLNode &node, bool reverse); @@ -94,6 +94,7 @@ private: const Vec3 &h1, const Vec3 &h2); float adjustTime(float time); float get(float time, unsigned int index, unsigned int n); + btQuaternion getOverallRotation(); }; // IpoData // ------------------------------------------------------------------------ @@ -123,7 +124,7 @@ public: float get(float time, unsigned int index) const; void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr); void reset(); - + btQuaternion getOverallRotation(); // ------------------------------------------------------------------------ /** Returns the raw data points for this IPO. */ const std::vector& getPoints() const { return m_ipo_data->m_points; } diff --git a/src/karts/cannon_animation.cpp b/src/karts/cannon_animation.cpp index 39ce1a933..036a7108a 100644 --- a/src/karts/cannon_animation.cpp +++ b/src/karts/cannon_animation.cpp @@ -27,8 +27,10 @@ #include "LinearMath/btTransform.h" -CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo) - : AbstractKartAnimation(kart, "CannonAnimation") +CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo, + const Vec3 &start_left, const Vec3 &start_right, + const Vec3 &end_left, const Vec3 &end_right ) + : AbstractKartAnimation(kart, "CannonAnimation") { m_curve = new AnimationBase(ipo); m_timer = ipo->getEndTime(); @@ -39,6 +41,12 @@ CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo) m_curve->update(0, &m_previous_orig_xyz); m_delta = kart->getXYZ() - m_previous_orig_xyz; + m_delta_rotation = + shortestArcQuatNormalize2(start_left - start_right, + end_left - end_right ); + + + // Now the delta vector needs to be rotated back, so that it will point // in the right direction when it is (in update) rotated to be the same // as the kart's heading. To estimate the angle at the start, use the @@ -46,11 +54,6 @@ CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo) const float dt = 0.1f; Vec3 xyz1; m_curve->update(dt, &xyz1); - core::vector3df rot = (m_previous_orig_xyz-xyz1).toIrrVector() - .getHorizontalAngle(); - btQuaternion q(Vec3(0,1,0),rot.Y*DEGREE_TO_RAD); - btMatrix3x3 m(q); - m_delta = m * m_delta; // The previous call to m_curve->update will set the internal timer // of the curve to dt. Reset it to 0 to make sure the timer is in @@ -104,7 +107,11 @@ void CannonAnimation::update(float dt) } m_previous_orig_xyz = xyz; - Vec3 rotated_delta = m_kart->getTrans().getBasis()*m_delta; + btQuaternion zero(0, 0, 0, 1); + // The timer count backwards, so the fraction goes from 1 to 0 + float f = m_timer / m_curve->getAnimationDuration(); + btQuaternion current_rot = m_delta_rotation.slerp(zero, f); + Vec3 rotated_delta = quatRotate(current_rot, m_delta); m_kart->setXYZ(xyz + rotated_delta); AbstractKartAnimation::update(dt); diff --git a/src/karts/cannon_animation.hpp b/src/karts/cannon_animation.hpp index bb8022c25..44019efea 100644 --- a/src/karts/cannon_animation.hpp +++ b/src/karts/cannon_animation.hpp @@ -22,15 +22,18 @@ #include "karts/abstract_kart_animation.hpp" #include "utils/vec3.hpp" -/** This animation shoots the kart to a specified point on the track. - * - * \ingroup karts - */ +#include "LinearMath/btQuaternion.h" class AbstractKart; class AnimationBase; class Ipo; + +/** This animation shoots the kart to a specified point on the track. + * + * \ingroup karts + */ + class CannonAnimation: public AbstractKartAnimation { protected: @@ -40,6 +43,10 @@ protected: * kart position (so the kart moves relative to the curve). */ Vec3 m_delta; + /** The amount of rotation to be applied to m_delta so that it keeps + * being on the 'right' side of the curve. */ + btQuaternion m_delta_rotation; + /** Stores the curve interpolation for the cannon. */ AnimationBase *m_curve; @@ -48,7 +55,9 @@ protected: Vec3 m_previous_orig_xyz; public: - CannonAnimation(AbstractKart *kart, Ipo *ipo); + CannonAnimation(AbstractKart *kart, Ipo *ipo, + const Vec3 &start_left, const Vec3 &start_right, + const Vec3 &end_left, const Vec3 &end_right); virtual ~CannonAnimation(); virtual void update(float dt); diff --git a/src/tracks/check_cannon.cpp b/src/tracks/check_cannon.cpp index 2fb3000c8..1d2f68a3d 100644 --- a/src/tracks/check_cannon.cpp +++ b/src/tracks/check_cannon.cpp @@ -35,13 +35,14 @@ CheckCannon::CheckCannon(const XMLNode &node, unsigned int index) : CheckLine(node, index) { - core::vector3df p1, p2; - if(!node.get("target-p1", &p1) || !node.get("target-p2", &p2)) + if( !node.get("target-p1", &m_target_left ) || + !node.get("target-p2", &m_target_right) ) Log::fatal("CheckCannon", "No target line specified."); - m_target.setLine(p1, p2); + m_curve = new Ipo(*(node.getNode("curve")), /*fps*/25, /*reverse*/race_manager->getReverseTrack()); + #if defined(DEBUG) && !defined(SERVER_ONLY) if(UserConfigParams::m_track_debug) { @@ -73,9 +74,9 @@ CheckCannon::~CheckCannon() */ void CheckCannon::trigger(unsigned int kart_index) { - Vec3 target(m_target.getMiddle()); AbstractKart *kart = World::getWorld()->getKart(kart_index); if(kart->getKartAnimation()) return; - new CannonAnimation(kart, m_curve->clone()); + new CannonAnimation(kart, m_curve->clone(), getLeftPoint(), getRightPoint(), + m_target_left, m_target_right); } // CheckCannon diff --git a/src/tracks/check_cannon.hpp b/src/tracks/check_cannon.hpp index 3507195d3..a57fb4893 100644 --- a/src/tracks/check_cannon.hpp +++ b/src/tracks/check_cannon.hpp @@ -37,7 +37,8 @@ class CheckCannon : public CheckLine { private: /** The target point the kart will fly to. */ - core::line3df m_target; + Vec3 m_target_left; + Vec3 m_target_right; /** Stores the cannon curve data. */ Ipo *m_curve; diff --git a/src/tracks/check_line.hpp b/src/tracks/check_line.hpp index 1da3ec34c..3b06e2ca8 100644 --- a/src/tracks/check_line.hpp +++ b/src/tracks/check_line.hpp @@ -71,6 +71,10 @@ private: /** How much a kart is allowed to be over the minimum height of a * quad and still considered to be able to cross it. */ static const int m_over_min_height = 4; +protected: + const Vec3 &getLeftPoint() const { return m_left_point; } + const Vec3 &getRightPoint() const { return m_right_point; } + public: CheckLine(const XMLNode &node, unsigned int index); virtual ~CheckLine(); From 41df6afbf9ed48b720c8a96afed6735eeeb9f625 Mon Sep 17 00:00:00 2001 From: hiker Date: Mon, 20 Feb 2017 09:22:09 +1100 Subject: [PATCH 064/378] Fixed compilation. --- src/karts/cannon_animation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/karts/cannon_animation.cpp b/src/karts/cannon_animation.cpp index 036a7108a..5f0666b50 100644 --- a/src/karts/cannon_animation.cpp +++ b/src/karts/cannon_animation.cpp @@ -41,9 +41,9 @@ CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo, m_curve->update(0, &m_previous_orig_xyz); m_delta = kart->getXYZ() - m_previous_orig_xyz; - m_delta_rotation = - shortestArcQuatNormalize2(start_left - start_right, - end_left - end_right ); + Vec3 v1 = start_left - start_right; + Vec3 v2 = end_left - end_right; + m_delta_rotation = shortestArcQuatNormalize2(v1, v2 ); From 875d5d8ef91260998c68c6fa3751ac2292754b7d Mon Sep 17 00:00:00 2001 From: Deve Date: Mon, 20 Feb 2017 21:19:00 +0100 Subject: [PATCH 065/378] Some tweaks to the arrows size in dynamic ribbon widget. - arrow size now depends on screen size, so that it's now easier to click it on high resolutions - it's additionally scaled on Android for easier usage --- src/guiengine/widgets/dynamic_ribbon_widget.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/guiengine/widgets/dynamic_ribbon_widget.cpp b/src/guiengine/widgets/dynamic_ribbon_widget.cpp index 548a1ab14..ae7cec447 100644 --- a/src/guiengine/widgets/dynamic_ribbon_widget.cpp +++ b/src/guiengine/widgets/dynamic_ribbon_widget.cpp @@ -17,6 +17,7 @@ #include "font/font_manager.hpp" #include "font/regular_face.hpp" +#include "graphics/irr_driver.hpp" #include "guiengine/engine.hpp" #include "guiengine/scalable_font.hpp" #include "guiengine/widgets/dynamic_ribbon_widget.hpp" @@ -146,8 +147,14 @@ void DynamicRibbonWidget::add() m_right_widget = new IconButtonWidget(IconButtonWidget::SCALE_MODE_KEEP_TEXTURE_ASPECT_RATIO, false); const int average_y = m_y + (m_h - m_label_height)/2; - m_arrows_w = 40; - const int button_h = 50; + + unsigned int screen_height = irr_driver->getActualScreenSize().Height; + m_arrows_w = (int)(screen_height / 15); + m_arrows_w = std::max(m_arrows_w, 40); +#ifdef ANDROID + m_arrows_w *= 1.5f; +#endif + const int button_h = m_arrows_w; // right arrow rect right_arrow_location = rect(m_x + m_w - m_arrows_w, From ef2cba0f8552916d39ce80387d3f9cd6402a3292 Mon Sep 17 00:00:00 2001 From: Deve Date: Mon, 20 Feb 2017 21:21:37 +0100 Subject: [PATCH 066/378] Display multitouch settings properly on low resolutions --- data/gui/multitouch_settings.stkgui | 131 ++++++++++++++-------------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/data/gui/multitouch_settings.stkgui b/data/gui/multitouch_settings.stkgui index 1cf3857bf..4ea7bd71d 100644 --- a/data/gui/multitouch_settings.stkgui +++ b/data/gui/multitouch_settings.stkgui @@ -4,76 +4,75 @@
- -