diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 7e3c09bbb4..59f1daada0 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -125,6 +125,7 @@ struct UserSettings { ConfigVar shadowResolutionMultiplier; ConfigVar enableDepthOfField; ConfigVar enableMapBackground; + ConfigVar disableCutscenePillarboxing; // Audio ConfigVar noLowHpSound; diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 266504d98c..99274f133d 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -11234,6 +11234,62 @@ cXyz dCamera_c::Center() { return mCenter + mShake.field_0x24; } +#ifdef TARGET_PC +f32 get_target_trim_height(camera_process_class* i_this) { + const auto camera = &i_this->mCamera; + if (camera->mCurState != 2) { + switch (camera->mTrimSize) { + case 0: + case 4: + return 0.0f; + case 1: + return camera->mCamSetup.VistaTrimHeight(); + case 2: + case 3: + return camera->mCamSetup.CinemaScopeTrimHeight(); + default: + return camera->mTrimHeight; + } + } + return camera->mTrimHeight; +} + +void widezoom_correction(camera_process_class* i_this, float trim_height) { + camera_class* camera = (camera_class*)i_this; + dDlst_window_c* window = get_window(camera); + view_port_class* viewport = window->getViewPort(); + + auto trim_width = 0.0f; + + if (mDoGph_gInf_c::isWideZoom()) { + const auto target_ar = FB_WIDTH_BASE / (FB_HEIGHT_BASE - trim_height * 2.0f); + const auto target_ar_real = + FB_WIDTH_BASE / (FB_HEIGHT_BASE - get_target_trim_height(i_this) * 2.0f); + const auto current_ar = camera->view.aspect; + + if (current_ar < target_ar) { + trim_height = FB_HEIGHT_BASE / 2.0f * (1.0f - current_ar / target_ar); + } else { + trim_height = 0.0f; + trim_width = FB_WIDTH_BASE / 2.0f * (1.0f - target_ar_real / current_ar); + } + + if (dusk::frame_interp::is_sim_frame()) { + constexpr auto base_ar = + static_cast(FB_WIDTH_BASE) / static_cast(FB_HEIGHT_BASE); + const auto ar_corr = base_ar / std::min(current_ar, target_ar_real); + camera->view.fovy = + MTXRadToDeg(2.0f * atanf(tanf(MTXDegToRad(camera->view.fovy) * 0.5f) * ar_corr)); + } + } + + trim_width *= viewport->width / FB_WIDTH_BASE; + trim_height *= viewport->height / FB_HEIGHT_BASE; + window->setScissor(trim_width, trim_height, viewport->width - trim_width * 2.0f, + viewport->height - trim_height * 2.0f); +} +#endif + static int camera_execute(camera_process_class* i_this) { preparation(i_this); @@ -11254,6 +11310,28 @@ static int camera_execute(camera_process_class* i_this) { store(i_this); #ifdef TARGET_PC + widezoom_correction(i_this, i_this->mCamera.TrimHeight()); + + if (dusk::getSettings().game.enableFrameInterpolation) { + dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) { + const auto i_this = static_cast(pUserWork); + const auto camera = &i_this->mCamera; + + const auto trim_size = camera->mTrimSize; + + if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) { + // derive trim height at previous tick using current camera state + const auto target = get_target_trim_height(i_this); + const auto step = dusk::frame_interp::get_interpolation_step(); + const auto cur = camera->TrimHeight(); + const auto prev = (4.0f * cur - target) / 3.0f; + const auto trim_height = prev + (cur - prev) * step; + + widezoom_correction(i_this, trim_height); + } + }, i_this); + } + // record new camera for our sim frame dusk::frame_interp::record_camera(i_this, get_camera_id(i_this)); // interpolate the view now so that this sim frame's view matrix matches what @@ -11265,26 +11343,6 @@ static int camera_execute(camera_process_class* i_this) { return 1; } -#ifdef TARGET_PC -void set_ar_corrected_trim(dDlst_window_c* window, float trim_height) { - const auto viewport = window->getViewPort(); - - if (mDoGph_gInf_c::isWideZoom()) { - const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f); - const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF; - - if (current_ar < target_ar) { - trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar); - } else { - trim_height = 0.0f; - } - } - - trim_height *= viewport->height / FB_HEIGHT; - window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f); -} -#endif - static int camera_draw(camera_process_class* i_this) { camera_class* a_this = (camera_class*)i_this; dCamera_c* body = &i_this->mCamera; @@ -11337,42 +11395,8 @@ static int camera_draw(camera_process_class* i_this) { } #endif -#if TARGET_PC - set_ar_corrected_trim(window, body->TrimHeight()); - - if (dusk::getSettings().game.enableFrameInterpolation) { - dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) { - const auto i_this = static_cast(pUserWork); - const auto camera = &i_this->mCamera; - - const auto trim_size = camera->mTrimSize; - - if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) { - // derive trim height at previous tick using current camera state - f32 target; - switch (trim_size) { - case 0: - target = 0.0f; - break; - case 1: - target = camera->mCamSetup.VistaTrimHeight(); - break; - case 2: - case 3: - target = camera->mCamSetup.CinemaScopeTrimHeight(); - break; - } - - const auto step = dusk::frame_interp::get_interpolation_step(); - const auto cur = camera->TrimHeight(); - const auto prev = (4.0f * cur - target) / 3.0f; - const auto trim_height = prev + (cur - prev) * step; - - set_ar_corrected_trim(get_window((camera_class*)i_this), trim_height); - } - }, i_this); - } -#else +#if !TARGET_PC + // trim handling moved to camera_execute for PC int trim_height = body->TrimHeight(); window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 27c37b4cfc..5944fe701b 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -62,6 +62,7 @@ UserSettings g_userSettings = { .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, .enableDepthOfField {"game.enableDepthOfField", true}, .enableMapBackground {"game.enableMapBackground", true}, + .disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -184,6 +185,7 @@ void registerSettings() { Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableDepthOfField); Register(g_userSettings.game.enableMapBackground); + Register(g_userSettings.game.disableCutscenePillarboxing); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.freeMagicArmor); diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index 3b3a45f950..33b02aa8da 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -738,6 +738,10 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { { .key = "Enable Mini-Map Shadows", }); + config_bool_select(leftPane, rightPane, getSettings().game.disableCutscenePillarboxing, + { + .key = "Disable Cutscene Pillarboxing", + }); }); add_tab("Input", [this](Rml::Element* content) { diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 424f69cf4b..c400b22266 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -636,7 +636,7 @@ u8 mDoGph_gInf_c::isWide() { } void mDoGph_gInf_c::setWideZoomProjection(Mtx44& m) { - if (!isWideZoom()) { + IF_NOT_DUSK(if (!isWideZoom())) { return; } @@ -682,7 +682,7 @@ void mDoGph_gInf_c::setWideZoomProjection(Mtx44& m) { } void mDoGph_gInf_c::setWideZoomLightProjection(Mtx& m) { - if (!isWideZoom()) { + IF_NOT_DUSK(if (!isWideZoom())) { return; } @@ -1189,14 +1189,24 @@ static void trimming(view_class* param_0, view_port_class* param_1) { ZoneScoped; UNUSED(param_0); + #if !TARGET_PC s16 y_orig = (int)param_1->y_orig & ~7; s16 y_orig_pos = y_orig < 0 ? 0 : y_orig; if ((y_orig_pos == 0) && (param_1->scissor.y_orig != param_1->y_orig || (param_1->scissor.height != param_1->height))) + #endif { #if TARGET_PC f32 sc_top = param_1->scissor.y_orig; - f32 sc_bottom = param_1->scissor.y_orig + param_1->scissor.height; + f32 sc_bottom = sc_top + param_1->scissor.height; + + f32 sc_left = 0.0f; + f32 sc_right = param_1->width; + + if (!dusk::getSettings().game.disableCutscenePillarboxing) { + sc_left = param_1->scissor.x_orig; + sc_right = sc_left + param_1->scissor.width; + } #else s32 sc_top = (int)param_1->scissor.y_orig; s32 sc_bottom = param_1->scissor.y_orig + param_1->scissor.height; @@ -1232,17 +1242,32 @@ static void trimming(view_class* param_0, view_port_class* param_1) { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, DUSK_IF_ELSE(GX_F32, GX_RGBA4), 0); GXSetProjection(ortho, GX_ORTHOGRAPHIC); GXSetCurrentMtx(0); - GXBegin(GX_QUADS, GX_VTXFMT0, 8); + GXBegin(GX_QUADS, GX_VTXFMT0, DUSK_IF_ELSE(16, 8)); #if TARGET_PC + // top trapezoid GXPosition3f32(0, 0, -5); GXPosition3f32(param_1->width, 0, -5); - GXPosition3f32(param_1->width, sc_top, -5); - GXPosition3f32(0, sc_top, -5); - GXPosition3f32(0, sc_bottom, -5); - GXPosition3f32(param_1->width, sc_bottom, -5); + GXPosition3f32(sc_right, sc_top, -5); + GXPosition3f32(sc_left, sc_top, -5); + + // bottom trapezoid + GXPosition3f32(sc_left, sc_bottom, -5); + GXPosition3f32(sc_right, sc_bottom, -5); GXPosition3f32(param_1->width, param_1->height, -5); GXPosition3f32(0, param_1->height, -5); + + // left trapezoid + GXPosition3f32(0, 0, -5); + GXPosition3f32(sc_left, sc_top, -5); + GXPosition3f32(sc_left, sc_bottom, -5); + GXPosition3f32(0, param_1->height, -5); + + // right trapezoid + GXPosition3f32(sc_right, sc_top, -5); + GXPosition3f32(param_1->width, 0, -5); + GXPosition3f32(param_1->width, param_1->height, -5); + GXPosition3f32(sc_right, sc_bottom, -5); #else GXPosition3s16(0, 0, -5); GXPosition3s16(FB_WIDTH, 0, -5);