Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions editor_patch/level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,12 @@ CodeInjection CLevelDialog_OnInitDialog_patch{
std::snprintf(buffer, sizeof(buffer), "%.3f", alpine_level_props.static_mesh_ambient_light_modifier);
SetDlgItemTextA(hdlg, IDC_MESH_AMBIENT_LIGHT_MODIFIER, buffer);
CheckDlgButton(hdlg, IDC_RF2_STYLE_GEOMOD, alpine_level_props.rf2_style_geomod ? BST_CHECKED : BST_UNCHECKED);

// Populate perspective dropdown
HWND combo = GetDlgItem(hdlg, IDC_PERSPECTIVE_COMBO);
SendMessageA(combo, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>("First person"));
SendMessageA(combo, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>("Side-scroller"));
SendMessageA(combo, CB_SETCURSEL, alpine_level_props.perspective, 0);
},
};

Expand All @@ -643,6 +649,11 @@ CodeInjection CLevelDialog_OnOK_patch{
alpine_level_props.static_mesh_ambient_light_modifier = modifier;
}
alpine_level_props.rf2_style_geomod = IsDlgButtonChecked(hdlg, IDC_RF2_STYLE_GEOMOD) == BST_CHECKED;

int sel = static_cast<int>(SendDlgItemMessageA(hdlg, IDC_PERSPECTIVE_COMBO, CB_GETCURSEL, 0, 0));
if (sel != CB_ERR) {
alpine_level_props.perspective = static_cast<uint8_t>(sel);
}
},
};

Expand Down
13 changes: 13 additions & 0 deletions editor_patch/level.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ struct AlpineLevelProperties
float static_mesh_ambient_light_modifier = 2.0f;
// v4
bool rf2_style_geomod = false;
uint8_t perspective = 0; // 0 = First person, 1 = Side-scroller
// v4 (continued - vectors kept after scalars for clean versioning)
std::vector<int32_t> geoable_brush_uids;
std::vector<int32_t> geoable_room_uids; // computed at save time, parallel to geoable_brush_uids
std::vector<int32_t> breakable_brush_uids;
Expand Down Expand Up @@ -377,6 +379,7 @@ struct AlpineLevelProperties
override_static_mesh_ambient_light_modifier = false;
static_mesh_ambient_light_modifier = 2.0f;
rf2_style_geomod = false;
perspective = 0;
geoable_brush_uids.clear();
geoable_room_uids.clear();
breakable_brush_uids.clear();
Expand Down Expand Up @@ -433,6 +436,7 @@ struct AlpineLevelProperties
uint8_t mat = (i < breakable_materials.size()) ? breakable_materials[i] : 0;
file.write<uint8_t>(mat);
}
file.write<std::uint8_t>(perspective);
}

void Deserialize(rf::File& file, std::size_t chunk_len)
Expand Down Expand Up @@ -545,6 +549,15 @@ struct AlpineLevelProperties
breakable_materials[i] = mat;
}
}

// perspective appended to v4
if (version >= 4) {
std::uint8_t u8 = 0;
if (!read_bytes(&u8, sizeof(u8)))
return;
perspective = u8;
xlog::debug("[AlpineLevelProps] perspective {}", perspective);
}
}
};

Expand Down
2 changes: 2 additions & 0 deletions editor_patch/resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#define IDC_MATERIAL_COMBO 2008
#define IDC_MATERIAL_LABEL 2009
#define IDC_NO_DEBRIS 2010
#define IDC_PERSPECTIVE_LABEL 2011
#define IDC_PERSPECTIVE_COMBO 2012

#define IDD_BRUSH_MODE_PANEL 205
#define IDD_BRUSH_PROPERTIES 270
Expand Down
6 changes: 4 additions & 2 deletions editor_patch/resources.rc
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ FONT 8, "MS Sans Serif", 0, 0, 1
}

LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
IDD_LEVEL_PROPERTIES DIALOGEX 0, 0, 298, 362
IDD_LEVEL_PROPERTIES DIALOGEX 0, 0, 298, 382
STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Level Properties"
FONT 8, "MS Sans Serif"
Expand Down Expand Up @@ -144,14 +144,16 @@ FONT 8, "MS Sans Serif"
DEFPUSHBUTTON "OK", IDOK, 241, 7, 50, 14
PUSHBUTTON "Cancel", IDCANCEL, 241, 24, 50, 14

GROUPBOX "Advanced Options", -1, 7, 268, 284, 90
GROUPBOX "Advanced Options", -1, 7, 268, 284, 110
AUTOCHECKBOX "Legacy Cyclic_Timer events", IDC_LEGACY_CYCLIC_TIMERS, 13, 280, 175, 10
AUTOCHECKBOX "Legacy movers", IDC_LEGACY_MOVERS, 13, 292, 175, 10
AUTOCHECKBOX "Player starts with headlamp", IDC_STARTS_WITH_HEADLAMP, 13, 304, 175, 10
AUTOCHECKBOX "Override static mesh ambient light scale", IDC_OVERRIDE_MESH_AMBIENT_LIGHT_MODIFIER, 13, 316, 190, 10
LTEXT "New scale value:", -1, 30, 331, 120, 8
EDITTEXT IDC_MESH_AMBIENT_LIGHT_MODIFIER, 90, 328, 30, 14, ES_AUTOHSCROLL
AUTOCHECKBOX "Brush-based geomod (RF2-style)", IDC_RF2_STYLE_GEOMOD, 13, 344, 200, 10
LTEXT "Perspective:", IDC_PERSPECTIVE_LABEL, 13, 360, 45, 8
COMBOBOX IDC_PERSPECTIVE_COMBO, 60, 358, 100, 60, WS_TABSTOP | CBS_DROPDOWNLIST
}

LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
Expand Down
2 changes: 2 additions & 0 deletions game_patch/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ set(SRCS
misc/destruction.cpp
misc/destruction.h
misc/camera.cpp
misc/side_scroller.cpp
misc/side_scroller.h
misc/ui.cpp
misc/game.cpp
misc/level.cpp
Expand Down
28 changes: 28 additions & 0 deletions game_patch/graphics/d3d11/gr_d3d11_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <cstring>
#include "../../rf/gr/gr_light.h"
#include "../../rf/os/frametime.h"
#include "../../misc/side_scroller.h"
#include "gr_d3d11.h"
#include "gr_d3d11_context.h"
#include "gr_d3d11_texture.h"
Expand Down Expand Up @@ -251,6 +252,16 @@ namespace df::gr::d3d11
float disable_textures;
std::array<float, 3> fog_color;
float pad0;
// Side-scroller occlusion (dithered transparency)
float ss_fade_strength;
float ss_radius;
float ss_is_detail;
float ss_num_entities;
// Each entity position stored as float4 (xyz + pad) for HLSL array packing
std::array<float, 4> ss_entity_pos[max_ss_occlusion_entities];
// Camera position for computing entity-to-camera cylinder axis
std::array<float, 3> ss_camera_pos;
float pad1;
};
static_assert(sizeof(RenderModeBufferData) % 16 == 0);

Expand Down Expand Up @@ -327,6 +338,23 @@ namespace df::gr::d3d11
data.disable_textures = current_lightmap_only_ ? 1.0f : 0.0f;
data.pad0 = 0.0f;

const auto& ss = get_ss_occlusion_params();
if (ss.active) {
data.ss_fade_strength = ss.fade_strength;
data.ss_radius = ss.radius;
data.ss_num_entities = static_cast<float>(ss.num_entities);
for (int i = 0; i < ss.num_entities; ++i) {
data.ss_entity_pos[i] = {ss.entity_pos[i].x, ss.entity_pos[i].y, ss.entity_pos[i].z, 0.0f};
}
data.ss_camera_pos = {ss.camera_pos.x, ss.camera_pos.y, ss.camera_pos.z};
}
else {
data.ss_fade_strength = 0.0f;
data.ss_radius = 0.0f;
data.ss_num_entities = 0.0f;
}
data.ss_is_detail = current_ss_is_detail_ ? 1.0f : 0.0f;

D3D11_MAPPED_SUBRESOURCE mapped_subres;
DF_GR_D3D11_CHECK_HR(
device_context->Map(buffer_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_subres)
Expand Down
18 changes: 17 additions & 1 deletion game_patch/graphics/d3d11/gr_d3d11_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "gr_d3d11_texture.h"
#include "gr_d3d11_state.h"
#include "../../misc/alpine_settings.h"
#include "../../misc/side_scroller.h"
#include "../../rf/gr/gr.h"

namespace df::gr::d3d11
Expand Down Expand Up @@ -86,7 +87,8 @@ namespace df::gr::d3d11
bool alpha_test = mode.get_zbuffer_type() == gr::ZBUFFER_TYPE_FULL_ALPHA_TEST;
bool fog_allowed = mode.get_fog_type() != gr::FOG_NOT_ALLOWED;
int colorblind_mode = g_alpine_game_config.colorblind_mode;
if (force_update_ || current_alpha_test_ != alpha_test || current_fog_allowed_ != fog_allowed || current_color_ != color || current_colorblind_mode_ != colorblind_mode || current_lightmap_only_ != lightmap_only) {
bool ss_active = get_ss_occlusion_params().active;
if (force_update_ || ss_active || current_alpha_test_ != alpha_test || current_fog_allowed_ != fog_allowed || current_color_ != color || current_colorblind_mode_ != colorblind_mode || current_lightmap_only_ != lightmap_only) {
current_alpha_test_ = alpha_test;
current_fog_allowed_ = fog_allowed;
current_color_ = color;
Expand All @@ -109,6 +111,14 @@ namespace df::gr::d3d11
}
}

void set_ss_is_detail(bool is_detail)
{
if (current_ss_is_detail_ != is_detail) {
current_ss_is_detail_ = is_detail;
force_update_ = true;
}
}

private:
void update_buffer(ID3D11DeviceContext* device_context);

Expand All @@ -119,6 +129,7 @@ namespace df::gr::d3d11
rf::Color current_color_{255, 255, 255};
int current_colorblind_mode_ = 0;
bool current_lightmap_only_ = false;
bool current_ss_is_detail_ = false;
};

class PerFrameBuffer
Expand Down Expand Up @@ -333,6 +344,11 @@ namespace df::gr::d3d11
per_frame_buffer_.update(device_context_);
}

void set_ss_is_detail(bool is_detail)
{
render_mode_cbuffer_.set_ss_is_detail(is_detail);
}

void fog_set()
{
render_mode_cbuffer_.handle_fog_change();
Expand Down
80 changes: 62 additions & 18 deletions game_patch/graphics/d3d11/gr_d3d11_solid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#undef NDEBUG

#include <windows.h>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <map>
Expand Down Expand Up @@ -254,6 +255,8 @@ namespace df::gr::d3d11
void add_face(GFace* face, GSolid* solid);
GRenderCache build(ID3D11Device* device);

void set_is_sky(bool is_sky) { is_sky_ = is_sky; }

int get_num_verts() const
{
return num_verts_;
Expand Down Expand Up @@ -305,18 +308,10 @@ namespace df::gr::d3d11
for (GFace& face : room->face_list) {
add_face(&face, solid);
}
// Only iterate detail_rooms for non-detail rooms. Detail rooms should never
// have sub-detail rooms in Red Faction. After RF2-style geomod, the boolean
// engine may corrupt the detail_rooms VArray on detail rooms, causing infinite
// recursion if we iterate it unconditionally.
if (!room->is_detail) {
for (GRoom* detail_room : room->detail_rooms) {
if (detail_room->face_list.empty()) {
continue; // skip destroyed breakable detail rooms
}
add_room(detail_room, solid);
}
}
// Detail rooms are rendered separately via render_detail() which sets the
// ss_is_detail flag for side-scroller dithered transparency. Including them
// here would cause double-rendering and break that effect. Sky rooms also
// render their detail rooms explicitly in render_sky_room().
}

static inline FaceRenderType determine_face_render_type(GFace* face)
Expand Down Expand Up @@ -746,29 +741,56 @@ namespace df::gr::d3d11
return cache;
}

void SolidRenderer::render_detail(rf::GSolid* solid, GRoom* room, bool alpha)
void SolidRenderer::render_detail(rf::GSolid* solid, GRoom* room, bool alpha, bool is_sky)
{
GRenderCache* cache = get_or_create_detail_room_cache(solid, room);
GRenderCache* cache = get_or_create_detail_room_cache(solid, room, is_sky);
if (!cache) return;
FaceRenderType render_type = alpha ? FaceRenderType::alpha : FaceRenderType::opaque;
render_context_.set_ss_is_detail(true);
cache->render(render_type, render_context_);
render_context_.set_ss_is_detail(false);
}

// Sentinel value stored in room->geo_cache to mark detail rooms that have been checked
// but have no renderable batches. Avoids rebuilding the cache builder every frame.
// clear_cache() resets all geo_cache to nullptr, which properly clears this sentinel.
static const auto k_empty_detail_sentinel = reinterpret_cast<rf::GCache*>(uintptr_t(1));

GRenderCache* SolidRenderer::get_or_create_detail_room_cache(rf::GSolid* solid, rf::GRoom* room)
GRenderCache* SolidRenderer::get_or_create_detail_room_cache(rf::GSolid* solid, rf::GRoom* room, bool is_sky)
{
auto cache = reinterpret_cast<GRenderCache*>(room->geo_cache);

// Check if existing cache needs invalidation due to sky mode mismatch
// (e.g. pre-cached as normal but now needed for a dynamic sky room set via Set_Skybox)
bool cached_as_sky = sky_detail_rooms_.count(room) > 0;
if (is_sky != cached_as_sky && (cache || room->geo_cache == k_empty_detail_sentinel)) {
if (cache) {
// Remove the old cache object from detail_render_cache_
auto it = std::find_if(detail_render_cache_.begin(), detail_render_cache_.end(),
[cache](const auto& ptr) { return ptr.get() == cache; });
if (it != detail_render_cache_.end()) {
detail_render_cache_.erase(it);
}
}
room->geo_cache = nullptr;
cache = nullptr;
}

if (room->geo_cache == k_empty_detail_sentinel) {
return nullptr;
}
auto cache = reinterpret_cast<GRenderCache*>(room->geo_cache);

if (!cache) {
xlog::debug("Creating render cache for detail room {} (faces: {})",
room->room_index, room->face_list.size());
xlog::debug("Creating render cache for detail room {} (faces: {} sky: {})",
room->room_index, room->face_list.size(), is_sky);
GRenderCacheBuilder builder;
if (is_sky) {
builder.set_is_sky(true);
sky_detail_rooms_.insert(room);
}
else {
sky_detail_rooms_.erase(room);
}
builder.add_room(room, solid);
xlog::debug("Detail room {} builder: verts={} inds={} batches={}",
room->room_index, builder.get_num_verts(), builder.get_num_inds(), builder.get_num_batches());
Expand Down Expand Up @@ -800,6 +822,7 @@ namespace df::gr::d3d11
detail_render_cache_.clear();
mover_render_cache_.clear();
geo_cache_rooms_.clear();
sky_detail_rooms_.clear();
geo_cache_num_rooms = 0;
xlog::debug("Room render cache clear complete");
}
Expand Down Expand Up @@ -830,7 +853,24 @@ namespace df::gr::d3d11
before_render(sky_room_offset, rf::identity_matrix);
}
render_room_faces(rf::level.geometry, room, FaceRenderType::opaque);

// Render detail rooms without frustum culling — skybox geometry is rendered
// with an offset/rotation so camera-based bounding box checks don't apply.
// All opaque detail faces must be drawn before any alpha detail faces to
// ensure correct blending when detail rooms overlap.
for (GRoom* detail_room : room->detail_rooms) {
if (!detail_room->face_list.empty()) {
render_detail(rf::level.geometry, detail_room, false, true);
}
}

render_room_faces(rf::level.geometry, room, FaceRenderType::alpha);

for (GRoom* detail_room : room->detail_rooms) {
if (!detail_room->face_list.empty()) {
render_detail(rf::level.geometry, detail_room, true, true);
}
}
render_context_.update_lights();
}

Expand All @@ -839,8 +879,10 @@ namespace df::gr::d3d11
xlog::trace("Rendering movable solid {}", solid);
GRenderCache* cache = get_or_create_movable_solid_cache(solid);
before_render(pos, orient);
render_context_.set_ss_is_detail(true);
cache->render(FaceRenderType::opaque, render_context_);
cache->render(FaceRenderType::alpha, render_context_);
render_context_.set_ss_is_detail(false);
if (decals_enabled) {
render_movable_solid_dynamic_decals(solid, pos, orient);
}
Expand Down Expand Up @@ -931,6 +973,8 @@ namespace df::gr::d3d11
}
}
for (rf::GRoom* room: solid->cached_detail_room_list) {
// Pre-cache as non-sky; if later rendered in a sky room context,
// get_or_create_detail_room_cache will detect the mismatch and rebuild.
get_or_create_detail_room_cache(solid, room);
}
}
Expand Down
Loading
Loading