Pillarbox widezoom cutscenes instead of cropping (#1054)

Fixes #777.
This commit is contained in:
doop
2026-05-12 00:59:17 -04:00
committed by GitHub
parent 79b1f4ab4d
commit 3366613354
5 changed files with 120 additions and 64 deletions
+1
View File
@@ -125,6 +125,7 @@ struct UserSettings {
ConfigVar<int> shadowResolutionMultiplier;
ConfigVar<bool> enableDepthOfField;
ConfigVar<bool> enableMapBackground;
ConfigVar<bool> disableCutscenePillarboxing;
// Audio
ConfigVar<bool> noLowHpSound;
+80 -56
View File
@@ -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<f32>(FB_WIDTH_BASE) / static_cast<f32>(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<camera_process_class*>(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<camera_process_class*>(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);
+2
View File
@@ -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);
+4
View File
@@ -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) {
+33 -8
View File
@@ -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);