diff --git a/src/supertux/gameconfig.cpp b/src/supertux/gameconfig.cpp index 76ea61416ea..67bbadc8f24 100644 --- a/src/supertux/gameconfig.cpp +++ b/src/supertux/gameconfig.cpp @@ -67,6 +67,7 @@ Config::Config() : flash_intensity(50), max_viewport(false), fancy_gfx(true), + enable_3d_mode(false), precise_scrolling(true), invert_wheel_x(false), invert_wheel_y(false), @@ -330,6 +331,7 @@ Config::load() config_video_mapping->get("magnification", magnification); config_video_mapping->get("fancy_gfx", fancy_gfx); + config_video_mapping->get("enable_3d_mode", enable_3d_mode); config_video_mapping->get("prefer_wayland", prefer_wayland); config_video_mapping->get("max_viewport", max_viewport); @@ -511,6 +513,7 @@ Config::save() writer.write("magnification", magnification); writer.write("fancy_gfx", fancy_gfx); + writer.write("enable_3d_mode", enable_3d_mode); writer.write("prefer_wayland", prefer_wayland); writer.write("max_viewport", max_viewport); diff --git a/src/supertux/gameconfig.hpp b/src/supertux/gameconfig.hpp index 1748ea1ed1d..16fab5ec1fa 100644 --- a/src/supertux/gameconfig.hpp +++ b/src/supertux/gameconfig.hpp @@ -90,6 +90,9 @@ class Config final /** Toggles fancy graphical effects like displacement or blur (primarily for the GL backend) */ bool fancy_gfx; + /** Enable 3D perspective mode */ + bool enable_3d_mode; + /** initial random seed. 0 ==> set from time() */ int random_seed; diff --git a/src/supertux/menu/options_menu.cpp b/src/supertux/menu/options_menu.cpp index b6d3b610cda..8386c061d2c 100644 --- a/src/supertux/menu/options_menu.cpp +++ b/src/supertux/menu/options_menu.cpp @@ -127,6 +127,9 @@ OptionsMenu::refresh() add_toggle(MNID_FANCY_GFX, _("Fancy Effects"), &g_config->fancy_gfx) .set_help(_("Applies fancy effects such as blur, clear tile refraction, and various other effects deemed \"fancy\". May significantly degrade performance.")); + add_toggle(MNID_3D_MODE, _("3D Mode"), &g_config->enable_3d_mode) + .set_help(_("Enables an isometric 3D perspective effect for the game.")); + add_flash_intensity(); #if !defined(HIDE_NONMOBILE_OPTIONS) && !defined(__EMSCRIPTEN__) diff --git a/src/supertux/menu/options_menu.hpp b/src/supertux/menu/options_menu.hpp index bb1ee14a266..8ce174e65d9 100644 --- a/src/supertux/menu/options_menu.hpp +++ b/src/supertux/menu/options_menu.hpp @@ -68,6 +68,7 @@ class OptionsMenu final : public Menu MNID_VSYNC, MNID_FRAME_PREDICTION, MNID_FANCY_GFX, + MNID_3D_MODE, MNID_SOUND, MNID_MUSIC, MNID_SOUND_VOLUME, diff --git a/src/video/gl/gl20_context.cpp b/src/video/gl/gl20_context.cpp index d8020fcd3a2..e7b07f6b7d7 100644 --- a/src/video/gl/gl20_context.cpp +++ b/src/video/gl/gl20_context.cpp @@ -67,6 +67,22 @@ GL20Context::ortho(float width, float height, bool vflip) glMatrixMode(GL_MODELVIEW); glLoadIdentity(); + + // Apply 3D isometric-like perspective transformation when enabled + if (g_config && g_config->enable_3d_mode) { + // True isometric projection with 30-degree angles for dramatic 3D effect + const GLfloat cos30 = 0.866f; // cos(30°) + const GLfloat sin30 = 0.5f; // sin(30°) + + // Isometric transformation matrix combining rotation and scaling + const GLfloat iso_matrix[] = { + cos30, sin30, 0.0f, 0.0f, // X axis rotated + -sin30, cos30 * 0.5f, 0.0f, 0.0f, // Y axis compressed and rotated + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + glMultMatrixf(iso_matrix); + } assert_gl(); } diff --git a/src/video/gl/gl33core_context.cpp b/src/video/gl/gl33core_context.cpp index 21812b318ee..8215008d677 100644 --- a/src/video/gl/gl33core_context.cpp +++ b/src/video/gl/gl33core_context.cpp @@ -125,11 +125,38 @@ GL33CoreContext::ortho(float width, float height, bool vflip) const float tx = -1.0f; const float ty = 1.0f * (vflip ? 1.0f : -1.0f); - const float mvp_matrix[] = { - sx, 0, tx, - 0, sy, ty, - 0, 0, 1 - }; + float mvp_matrix[9]; + + // Apply 3D isometric-like perspective transformation when enabled + if (g_config && g_config->enable_3d_mode) { + // True isometric projection with 30-degree angles + // This creates a dramatic 3D appearance by rotating the view + const float cos30 = 0.866f; // cos(30°) + const float sin30 = 0.5f; // sin(30°) + + // Combine rotation and skew for isometric effect + // X stays the same, Y is compressed and offset by X + mvp_matrix[0] = sx * cos30; // X scale with rotation + mvp_matrix[1] = sx * sin30; // X contribution to Y + mvp_matrix[2] = tx; + mvp_matrix[3] = -sy * sin30; // Y contribution to X (creates depth) + mvp_matrix[4] = sy * cos30 * 0.5f; // Y scale (compressed for isometric) + mvp_matrix[5] = ty; + mvp_matrix[6] = 0; + mvp_matrix[7] = 0; + mvp_matrix[8] = 1; + } else { + // Standard orthographic projection + mvp_matrix[0] = sx; + mvp_matrix[1] = 0; + mvp_matrix[2] = tx; + mvp_matrix[3] = 0; + mvp_matrix[4] = sy; + mvp_matrix[5] = ty; + mvp_matrix[6] = 0; + mvp_matrix[7] = 0; + mvp_matrix[8] = 1; + } const GLint mvp_loc = m_program->get_modelviewprojection_location(); glUniformMatrix3fv(mvp_loc, 1, false, mvp_matrix);