Skip to content

Commit

Permalink
Automatically find the appropriate UltraWide FOV. (#835)
Browse files Browse the repository at this point in the history
-Also works for 4:3 (anything below 16:9)
-Improves 21:9 support as the officially supported FOV was wrong
-Should work with all RE Engine games?
-Retains the FOV multiplier as custom option in case we didn't want the correct FOV for the AR to be used
-Restores the Vertical FOV when needed if the game already used it
  • Loading branch information
Filoppi committed Jul 3, 2023
1 parent d9c34b8 commit f0f73b2
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 42 deletions.
152 changes: 114 additions & 38 deletions src/mods/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,12 @@ void Graphics::on_draw_ui() {
ImGui::SetNextItemOpen(true, ImGuiCond_::ImGuiCond_Once);
if (ImGui::TreeNode("Ultrawide/FOV Options")) {
if (m_ultrawide_fix->draw("Ultrawide/FOV/Aspect Ratio Fix") && m_ultrawide_fix->value() == false) {
set_vertical_fov(false);
do_ultrawide_fov_restore(true);
}

if (m_ultrawide_fix->value()) {
m_ultrawide_vertical_fov->draw("Ultrawide: Enable Vertical FOV");
m_ultrawide_fov->draw("Ultrawide: Override FOV");
m_ultrawide_custom_fov->draw("Ultrawide: Override FOV");
m_ultrawide_fov_multiplier->draw("Ultrawide: FOV Multiplier");
}

Expand Down Expand Up @@ -360,20 +359,19 @@ void Graphics::do_ultrawide_fix() {
return;
}

set_ultrawide_fov(m_ultrawide_vertical_fov->value());

#if defined(RE4)
{
std::shared_lock _{m_re4.time_mtx};

const auto now = std::chrono::steady_clock::now();
if (now - m_re4.last_inventory_open < std::chrono::milliseconds(100)) {
set_vertical_fov(false);
return;
}
}
#endif

set_vertical_fov(m_ultrawide_vertical_fov->value());

static auto via_scene_view = sdk::find_type_definition("via.SceneView");
static auto set_display_type_method = via_scene_view->get_method("set_DisplayType");

Expand All @@ -383,6 +381,8 @@ void Graphics::do_ultrawide_fix() {
return;
}

// This disables any kind of pillarboxing and letterboxing.
// This cannot be directly restored once applied.
if (set_display_type_method != nullptr) {
set_display_type_method->call(sdk::get_thread_context(), main_view, via::DisplayType::Fit);
}
Expand All @@ -398,7 +398,7 @@ void Graphics::do_ultrawide_fov_restore(bool force) {
return;
}

#if defined(RE4)
#if defined(RE4) // Don't restore the FOV if we've just opened the inventory
const auto now = std::chrono::steady_clock::now();
if (now - m_re4.last_inventory_open < std::chrono::milliseconds(100)) {
return;
Expand All @@ -407,27 +407,57 @@ void Graphics::do_ultrawide_fov_restore(bool force) {

static auto via_camera = sdk::find_type_definition("via.Camera");
static auto set_fov_method = via_camera->get_method("set_FOV");
static auto set_vertical_enable_method = via_camera->get_method("set_VerticalEnable");

if (set_fov_method != nullptr && m_ultrawide_fov->value()) {
std::scoped_lock _{m_fov_mutex};
std::scoped_lock _{m_fov_mutex};

if (set_fov_method != nullptr) {
for (auto it : m_fov_map) {
auto camera = it.first;
set_fov_method->call(sdk::get_thread_context(), camera, m_fov_map[camera]);
utility::re_managed_object::release(camera);
}

m_fov_map.clear();
}

if (set_vertical_enable_method != nullptr) {
for (auto it : m_vertical_fov_map) {
auto camera = it.first;
set_vertical_enable_method->call(sdk::get_thread_context(), camera, m_vertical_fov_map[camera]);
utility::re_managed_object::release(camera);
}
m_vertical_fov_map.clear();
}
}

float hor_to_ver_fov(float fov, float aspect_ratio) {
return glm::degrees(2.f * glm::atan(glm::tan(glm::radians(fov) / 2.f) / aspect_ratio));
}
float ver_to_hor_fov(float fov, float aspect_ratio) {
return glm::degrees(2.f * glm::atan(glm::tan(glm::radians(fov) / 2.f) * aspect_ratio));
}

void Graphics::set_vertical_fov(bool enable) {
void Graphics::set_ultrawide_fov(bool use_vertical_fov) {
auto camera = sdk::get_primary_camera();

if (camera == nullptr) {
return;
}

bool allow_changing_fov = true;
#if defined(RE4)
// Never scale the FOV if the inventory just opened, otherwise it could make the inventory appear much smaller than it should.
// Unfortunately it doesn't scale right at 21:9 even in the unpatched game.
const auto now = std::chrono::steady_clock::now();
if (now - m_re4.last_inventory_open < std::chrono::milliseconds(100)) {
allow_changing_fov = false;
use_vertical_fov = false;
// Clear the cached FOV values as they wouldn't be up to date anymore
std::scoped_lock _{m_fov_mutex};
m_fov_map.clear();
}
#endif

static auto via_camera = sdk::find_type_definition("via.Camera");
static auto get_vertical_enable_method = via_camera->get_method("get_VerticalEnable");
static auto set_vertical_enable_method = via_camera->get_method("set_VerticalEnable");
Expand All @@ -436,51 +466,97 @@ void Graphics::set_vertical_fov(bool enable) {
static auto get_aspect_method = via_camera->get_method("get_AspectRatio");

bool was_vertical_fov_enabled = false;
bool is_vertical_fov_enabled = false;

if (get_vertical_enable_method != nullptr) {
was_vertical_fov_enabled = get_vertical_enable_method->call<bool>(sdk::get_thread_context(), camera);

{
std::scoped_lock _{m_fov_mutex};

if (!m_vertical_fov_map.contains(camera)) {
m_vertical_fov_map[camera] = was_vertical_fov_enabled;
utility::re_managed_object::add_ref(camera);
} else {
m_vertical_fov_map[camera] = was_vertical_fov_enabled;
}
}
}

if (set_vertical_enable_method != nullptr) {
set_vertical_enable_method->call(sdk::get_thread_context(), camera, enable);
set_vertical_enable_method->call(sdk::get_thread_context(), camera, use_vertical_fov);
is_vertical_fov_enabled = use_vertical_fov;
}


#if defined(RE4)
const auto now = std::chrono::steady_clock::now();
if (now - m_re4.last_inventory_open < std::chrono::milliseconds(100)) {
if (!allow_changing_fov || get_fov_method == nullptr || set_fov_method == nullptr) {
return;
}
#endif

if (m_ultrawide_fov->value() && get_fov_method != nullptr && set_fov_method != nullptr && get_aspect_method != nullptr) {
const auto fov = get_fov_method->call<float>(sdk::get_thread_context(), camera);
// This is usually the horizontal FOV but it could also be vertical.
const auto fov = get_fov_method->call<float>(sdk::get_thread_context(), camera);

{
std::scoped_lock _{m_fov_mutex};
{
std::scoped_lock _{m_fov_mutex};

if (!m_fov_map.contains(camera)) {
m_fov_map[camera] = fov;
utility::re_managed_object::add_ref(camera);
} else {
m_fov_map[camera] = fov;
}
if (!m_fov_map.contains(camera)) {
m_fov_map[camera] = fov;
utility::re_managed_object::add_ref(camera);
} else {
m_fov_map[camera] = fov;
}
}

if (!was_vertical_fov_enabled) {
constexpr auto old_aspect_ratio = 16.0f / 9.0f;
// convert hfov to vfov in radians
const auto vfov_og = 2.0f * glm::atan(glm::tan(glm::radians(fov / 2.0f)) / old_aspect_ratio);

const auto new_aspect_ratio = get_aspect_method->call<float>(sdk::get_thread_context(), camera);
// convert vfov to hfov for new aspect ratio and convert to degrees
const auto new_hfov = glm::degrees(2.0f * glm::atan(glm::tan(vfov_og / 2.0f) * new_aspect_ratio)) * m_ultrawide_fov_multiplier->value();
// Customize UW FOV with multiplier
if (m_ultrawide_custom_fov->value()) {
// Note: values a certain beyond a certain range might be rejected.
const auto scaled_fov = std::clamp(fov * m_ultrawide_fov_multiplier->value(), 1.f, 179.f);
set_fov_method->call(sdk::get_thread_context(), camera, scaled_fov);
}
// Automatically patch the FOV to make it look as it does at 16:9 (Hor+ scaling, thus fixed Ver FOV)
else {
constexpr float default_aspect_ratio = 16.f / 9.f;
// The threshold for letter boxing
constexpr float min_supported_aspect_ratio = default_aspect_ratio;
// The threshold for pillar boxing (or shifting to Ver- FOV)
#if defined(RE8)
constexpr float max_supported_aspect_ratio = 32.f / 9.f;
#elif defined(RE2) || defined(RE3) || defined(RE4)
// Even if most 21:9 resolutions actually have a higher aspect ratio than 2.333, that's actually what some games wrongfully use
constexpr float max_supported_aspect_ratio = 21.f / 9.f;
#else
constexpr float max_supported_aspect_ratio = default_aspect_ratio;
#endif

set_fov_method->call(sdk::get_thread_context(), camera, new_hfov);
} else { // No need to calculate new hfov if vertical fov is enabled by the engine itself.
const auto new_vfov = fov * m_ultrawide_fov_multiplier->value();
float current_aspect_ratio = default_aspect_ratio;
float target_aspect_ratio = default_aspect_ratio;
// The backbuffer doesn't always represent the game internal aspect ratio, as it also accounts for black bars.
// For example, when set to borderless and using a game resolution different form the current monitor one, the black
// bars on the side will be accounted in it, which is why we use it to calculate the target aspect ratio.
if (m_backbuffer_size.has_value()) {
const float resolution_x = (float)(*m_backbuffer_size)[0];
const float resolution_y = (float)(*m_backbuffer_size)[1];
target_aspect_ratio = resolution_x / resolution_y;
}
// The camera aspect ratio represents the aspect ratio the game uses within the black bars
if (get_aspect_method) {
current_aspect_ratio = get_aspect_method->call<float>(sdk::get_thread_context(), camera);
}
// Depending on the game, the FOV might have already automatically scaled up to "max_supported_aspect_ratio" by the time it reaches here
const float fov_aspect_ratio = std::clamp(current_aspect_ratio, min_supported_aspect_ratio, max_supported_aspect_ratio);

set_fov_method->call(sdk::get_thread_context(), camera, new_vfov);
if (was_vertical_fov_enabled) {
// Nothing to do, the FOV should already be correct under any aspect ratio in this case
}
else if (target_aspect_ratio >= min_supported_aspect_ratio) {
const auto vfov = hor_to_ver_fov(fov, fov_aspect_ratio);
const auto hfov_corrected = ver_to_hor_fov(vfov, target_aspect_ratio);
set_fov_method->call(sdk::get_thread_context(), camera, is_vertical_fov_enabled ? vfov : hfov_corrected);
}
// Account for the letter boxing and expand the vertical aspect ratio
else {
const auto vfov = hor_to_ver_fov(fov, target_aspect_ratio);
const auto hfov_corrected = ver_to_hor_fov(vfov, min_supported_aspect_ratio);
set_fov_method->call(sdk::get_thread_context(), camera, is_vertical_fov_enabled ? vfov : hfov_corrected);
}
}
}
9 changes: 5 additions & 4 deletions src/mods/Graphics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@ class Graphics : public Mod {
void do_scope_tweaks(sdk::renderer::layer::Scene* layer);
void do_ultrawide_fix();
void do_ultrawide_fov_restore(bool force = false);
void set_vertical_fov(bool enable);
void set_ultrawide_fov(bool enable);

std::recursive_mutex m_fov_mutex{};
std::unordered_map<::REManagedObject*, float> m_fov_map{};
std::unordered_map<::REManagedObject*, bool> m_vertical_fov_map{};

struct {
std::shared_mutex time_mtx{};
std::chrono::steady_clock::time_point last_inventory_open{};
} m_re4;

const ModToggle::Ptr m_ultrawide_fix{ ModToggle::create(generate_name("UltrawideFix"), false) };
const ModToggle::Ptr m_ultrawide_vertical_fov{ ModToggle::create(generate_name("UltrawideFixVerticalFOV_V2"), false) };
const ModToggle::Ptr m_ultrawide_fov{ ModToggle::create(generate_name("UltrawideFixFOV"), true) };
const ModToggle::Ptr m_ultrawide_custom_fov{ModToggle::create(generate_name("UltrawideCustomFOV"), false)};
const ModSlider::Ptr m_ultrawide_fov_multiplier{ ModSlider::create(generate_name("UltrawideFOVMultiplier_V2"), 0.01f, 3.0f, 1.0f) };
const ModToggle::Ptr m_disable_gui{ ModToggle::create(generate_name("DisableGUI"), false) };
const ModToggle::Ptr m_force_render_res_to_window{ ModToggle::create(generate_name("ForceRenderResToWindow"), false) };
Expand All @@ -61,7 +62,7 @@ class Graphics : public Mod {
ValueList m_options{
*m_ultrawide_fix,
*m_ultrawide_vertical_fov,
*m_ultrawide_fov,
*m_ultrawide_custom_fov,
*m_ultrawide_fov_multiplier,
*m_disable_gui,
*m_force_render_res_to_window,
Expand Down

0 comments on commit f0f73b2

Please sign in to comment.