tr2/output: make fog distances customizable

Resolves #1622.
This commit is contained in:
Marcin Kurczewski 2025-04-09 23:05:23 +02:00
parent 57bc7610b4
commit a69b6406d0
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
22 changed files with 191 additions and 103 deletions

View file

@ -100,12 +100,12 @@ remains distinct for each game.
<a name="draw-distance-fade"></a>
<code>fog_start</code>
</td>
<td>Double<strong>*</strong></td>
<td>Double</td>
<td>
The distance (in tiles) at which objects and the world start to fade into
blackness.
<ul>
<li>The default hardcoded value in TR1 is 12.</li>
<li>The default value in OG TR1 is hardcoded to 12.</li>
<li>The default (disabled) value in TombATI is 72.</li>
</ul>
</td>
@ -115,11 +115,11 @@ remains distinct for each game.
<a name="draw-distance-max"></a>
<code>fog_end</code>
</td>
<td>Double<strong>*</strong></td>
<td>Double</td>
<td>
The distance (in tiles) at which objects and the world are clipped away.
<ul>
<li>The default hardcoded value in TR1 is 20.</li>
<li>The default value in OG TR1 is hardcoded to 20.</li>
<li>The default (disabled) value in TombATI is 80.</li>
</ul>
</td>
@ -315,6 +315,28 @@ remains distinct for each game.
The path to the sound effects (.sfx) file to use in the game.
</td>
</tr>
<tr valign="top">
<td>
<a name="draw-distance-fade"></a>
<code>fog_start</code>
</td>
<td>Double</td>
<td>
The distance (in tiles) at which objects and the world start to fade into
blackness. The default value in OG TR2 is hardcoded to 12.
</td>
</tr>
<tr valign="top">
<td>
<a name="draw-distance-max"></a>
<code>fog_end</code>
</td>
<td>Double<strong>*</strong></td>
<td>
The distance (in tiles) at which objects and the world are clipped away.
The default value in OG TR2 is hardcoded to 20.
</td>
</tr>
</table>
## Game flow commands
@ -517,7 +539,7 @@ Following are each of the properties available within a level.
<td colspan="2">The ambient music track ID.</td>
</tr>
<tr valign="top">
<td><code>fog_start</code><strong>¹</strong></td>
<td><code>fog_start</code></td>
<td>Double</td>
<td colspan="2">
Can be customized per level. See <a href="#draw-distance-fade">above</a>
@ -525,7 +547,7 @@ Following are each of the properties available within a level.
</td>
</tr>
<tr valign="top">
<td><code>fog_end</code><strong>¹</strong></td>
<td><code>fog_end</code></td>
<td>Double</td>
<td colspan="2">
Can be customized per level. See <a href="#draw-distance-max">above</a>

View file

@ -1,5 +1,6 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-0.10...develop) - ××××-××-××
- added support for The Golden Mask (#1621)
- added control over the fog distances for players and level builders (#1622)
- added an installer for Windows (#2681)
- added the bonus level game flow type, which allows for levels to be unlocked if all main game secrets are found (#2668)
- added the ability for custom levels to have up to two of each secret type per level (#2674)

View file

@ -305,6 +305,7 @@ as Notepad.
#### Mods
- added developer console (accessible with `/`, see [COMMANDS.md](COMMANDS.md) for details)
- added ability to disable FMVs
- added per-level customizable fog distance
#### Miscellaneous
- added Linux builds

View file

@ -22,6 +22,8 @@ CFG_BOOL(g_Config, visuals.enable_exit_fade_effects, true)
CFG_BOOL(g_Config, visuals.fix_item_rots, true)
CFG_INT32(g_Config, visuals.fov, 80)
CFG_BOOL(g_Config, visuals.use_pcx_fov, true)
CFG_INT32(g_Config, visuals.fog_start, 12)
CFG_INT32(g_Config, visuals.fog_end, 20)
CFG_DOUBLE(g_Config, ui.text_scale, 1.0)
CFG_DOUBLE(g_Config, ui.bar_scale, 1.0)
CFG_BOOL(g_Config, ui.enable_photo_mode_ui, true)

View file

@ -71,6 +71,7 @@ static void M_LoadFMV(
void *user_arg);
static void M_LoadFMVs(JSON_OBJECT *obj, GAME_FLOW *gf);
static void M_LoadGlobalInjections(JSON_OBJECT *obj, GAME_FLOW *gf);
static void M_LoadCommonSettings(JSON_OBJECT *obj, GF_LEVEL_SETTINGS *settings);
static void M_LoadCommonRoot(JSON_OBJECT *obj, GAME_FLOW *gf);
static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf);
@ -80,6 +81,28 @@ static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf);
#include "./reader_tr2.def.c"
#endif
static void M_LoadCommonSettings(
JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings)
{
{
const double value =
JSON_ObjectGetDouble(obj, "fog_start", JSON_INVALID_NUMBER);
if (value != JSON_INVALID_NUMBER) {
settings->fog_start.is_present = true;
settings->fog_start.value = value;
}
}
{
const double value =
JSON_ObjectGetDouble(obj, "fog_end", JSON_INVALID_NUMBER);
if (value != JSON_INVALID_NUMBER) {
settings->fog_end.is_present = true;
settings->fog_end.value = value;
}
}
}
static void M_LoadCommonRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf)
{
const char *tmp_s =

View file

@ -98,23 +98,7 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent)
static void M_LoadSettings(
JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings)
{
{
const double value =
JSON_ObjectGetDouble(obj, "fog_start", JSON_INVALID_NUMBER);
if (value != JSON_INVALID_NUMBER) {
settings->fog_start.is_present = true;
settings->fog_start.value = value;
}
}
{
const double value =
JSON_ObjectGetDouble(obj, "fog_end", JSON_INVALID_NUMBER);
if (value != JSON_INVALID_NUMBER) {
settings->fog_end.is_present = true;
settings->fog_end.value = value;
}
}
M_LoadCommonSettings(obj, settings);
{
JSON_ARRAY *const tmp_arr = JSON_ObjectGetArray(obj, "water_color");

View file

@ -47,6 +47,8 @@ static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = {
static void M_LoadSettings(
JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings)
{
M_LoadCommonSettings(obj, settings);
{
const char *tmp_s =
JSON_ObjectGetString(obj, "sfx_path", JSON_INVALID_STRING);

View file

@ -10,6 +10,7 @@ typedef struct {
int32_t shade;
} COMMON_LIGHT;
static int32_t m_FogStart = 0;
static int32_t m_DynamicLightCount = 0;
static LIGHT m_DynamicLights[MAX_DYNAMIC_LIGHTS] = {};
@ -294,3 +295,13 @@ void Output_AddDynamicLight(
light->shade.value_1 = intensity;
light->falloff.value_1 = falloff;
}
int32_t Output_GetFogStart(void)
{
return m_FogStart;
}
void Output_SetFogStart(const int32_t dist)
{
m_FogStart = dist;
}

View file

@ -50,6 +50,8 @@ typedef struct {
bool fix_item_rots;
int32_t fov;
bool use_pcx_fov;
int32_t fog_start;
int32_t fog_end;
} visuals;
struct {

View file

@ -78,15 +78,15 @@ typedef struct {
} GF_FMV;
typedef struct {
struct {
bool is_present;
float value;
} fog_start, fog_end;
#if TR_VERSION == 1
struct {
bool is_present;
RGB_888 value;
} water_color;
struct {
bool is_present;
float value;
} fog_start, fog_end;
#elif TR_VERSION == 2
char *sfx_path;
#endif

View file

@ -2,6 +2,9 @@
#include "../rooms.h"
extern void Output_ObserveLevelLoad(void);
extern void Output_ObserveLevelUnload(void);
extern bool Output_MakeScreenshot(const char *path);
extern void Output_BeginScene(void);
extern void Output_EndScene(void);
@ -36,3 +39,8 @@ void Output_LightRoom(ROOM *room);
void Output_ResetDynamicLights(void);
void Output_AddDynamicLight(XYZ_32 pos, int32_t intensity, int32_t falloff);
int32_t Output_GetFogStart(void);
void Output_SetFogStart(int32_t dist);
extern int32_t Output_GetFogEnd(void);
extern void Output_SetFogEnd(int32_t dist);

View file

@ -388,8 +388,8 @@ void Output_ApplyLevelSettings(void)
{
const RGB_888 color = Level_GetWaterColor();
Output_SetWaterColor(Level_GetWaterColor());
Output_SetDrawDistFade(Level_GetFogStart() * WALL_L);
Output_SetDrawDistMax(Level_GetFogEnd() * WALL_L);
Output_SetFogStart(Level_GetFogStart() * WALL_L);
Output_SetFogEnd(Level_GetFogEnd() * WALL_L);
}
void Output_ApplyRenderSettings(void)

View file

@ -14,17 +14,10 @@ void Output_Shutdown(void);
void Output_SetWindowSize(int32_t width, int32_t height);
void Output_ApplyLevelSettings(void);
void Output_ApplyRenderSettings(void);
void Output_ObserveLevelLoad(void);
void Output_ObserveLevelUnload(void);
void Output_ObserveFOVChange(void);
int32_t Output_GetNearZ(void);
int32_t Output_GetFarZ(void);
int32_t Output_GetDrawDistMin(void);
int32_t Output_GetDrawDistFade(void);
int32_t Output_GetDrawDistMax(void);
void Output_SetDrawDistFade(int32_t dist);
void Output_SetDrawDistMax(int32_t dist);
void Output_SetWaterColor(const RGB_888 color);
void Output_BeginScene(void);

View file

@ -120,8 +120,8 @@ void Output_Shader_UploadCommonUniforms(const OUTPUT_SHADER *const shader)
glUniform2f, shader->uniforms[M_UNIFORM_VIEWPORT_SIZE],
GFX_Context_GetDisplayWidth(), GFX_Context_GetDisplayHeight());
GFX_TRACK_UNIFORM(
glUniform2f, shader->uniforms[M_UNIFORM_FOG], Output_GetDrawDistFade(),
Output_GetDrawDistMax());
glUniform2f, shader->uniforms[M_UNIFORM_FOG], Output_GetFogStart(),
Output_GetFogEnd());
GFX_TRACK_UNIFORM(
glUniform1i, shader->uniforms[M_UNIFORM_TIME], Output_GetTime());
}

View file

@ -6,9 +6,7 @@
static int32_t m_Time = 0;
static int32_t m_AnimatedTexturesOffset = 0;
static int32_t m_DrawDistFade = 0;
static int32_t m_DrawDistMax = 0;
static int32_t m_FogEnd = 0;
static RGB_F m_WaterColor = {};
static int32_t m_LsAdder = 0;
@ -19,29 +17,14 @@ static bool m_IsWibbleEffect = false;
static bool m_IsWaterEffect = false;
static bool m_IsShadeEffect = false;
int32_t Output_GetDrawDistMin(void)
int32_t Output_GetFogEnd(void)
{
return 20;
return m_FogEnd;
}
int32_t Output_GetDrawDistFade(void)
void Output_SetFogEnd(const int32_t dist)
{
return m_DrawDistFade;
}
int32_t Output_GetDrawDistMax(void)
{
return m_DrawDistMax;
}
void Output_SetDrawDistFade(const int32_t dist)
{
m_DrawDistFade = dist;
}
void Output_SetDrawDistMax(const int32_t dist)
{
m_DrawDistMax = dist;
m_FogEnd = dist;
const double near_z = Output_GetNearZ();
const double far_z = Output_GetFarZ();
@ -52,12 +35,12 @@ void Output_SetDrawDistMax(const int32_t dist)
int32_t Output_GetNearZ(void)
{
return Output_GetDrawDistMin() << W2V_SHIFT;
return 20 << W2V_SHIFT;
}
int32_t Output_GetFarZ(void)
{
return Output_GetDrawDistMax() << W2V_SHIFT;
return Output_GetFogEnd() << W2V_SHIFT;
}
void Output_SetupBelowWater(const bool underwater)

View file

@ -23,6 +23,7 @@
#include "global/vars.h"
#include <libtrx/benchmark.h>
#include <libtrx/config.h>
#include <libtrx/debug.h>
#include <libtrx/engine/audio.h>
#include <libtrx/filesystem.h>
@ -276,7 +277,7 @@ static void M_CompleteSetup(const GF_LEVEL *const level)
Level_LoadTexturePages();
Level_LoadPalettes();
Level_LoadFaces();
Output_InitialiseNamedColors();
Output_ObserveLevelLoad();
Render_Reset(
RENDER_RESET_PALETTE | RENDER_RESET_TEXTURES | RENDER_RESET_UVS);
@ -376,13 +377,7 @@ bool Level_Initialise(
void Level_Unload(void)
{
Output_InitialiseTexturePages(0, true);
Output_InitialiseObjectTextures(0);
if (Output_GetBackgroundType() == BK_OBJECT) {
Output_UnloadBackground();
}
Output_ObserveLevelUnload();
Camera_Reset();
}
@ -415,3 +410,21 @@ void Level_Init(void)
Benchmark_End(&benchmark, nullptr);
}
float Level_GetFogStart(void)
{
const GF_LEVEL *const level = GF_GetCurrentLevel();
if (level != nullptr && level->settings.fog_start.is_present) {
return level->settings.fog_start.value;
}
return g_Config.visuals.fog_start;
}
float Level_GetFogEnd(void)
{
const GF_LEVEL *const level = GF_GetCurrentLevel();
if (level != nullptr && level->settings.fog_end.is_present) {
return level->settings.fog_end.value;
}
return g_Config.visuals.fog_end;
}

View file

@ -8,3 +8,6 @@ void Level_Init(void);
bool Level_Initialise(const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx);
bool Level_Load(const GF_LEVEL *level);
void Level_Unload(void);
float Level_GetFogStart(void);
float Level_GetFogEnd(void);

View file

@ -2,11 +2,13 @@
#include "game/clock.h"
#include "game/inventory_ring.h"
#include "game/level.h"
#include "game/random.h"
#include "game/render/common.h"
#include "game/render/priv.h"
#include "game/scaler.h"
#include "game/shell.h"
#include "game/viewport.h"
#include "global/vars.h"
#include <libtrx/config.h>
@ -62,10 +64,14 @@ static int16_t m_ShadesTable[32];
static int32_t m_RandomTable[32];
static BACKGROUND_TYPE m_BackgroundType = BK_TRANSPARENT;
static XYZ_32 m_LsVectorView = {};
static int32_t m_FogEnd = 0;
static bool m_IsWaterEffect = false;
static bool m_IsWibbleEffect = false;
static bool m_IsShadeEffect = false;
static int32_t m_WibbleOffset = 0;
static bool m_IsSunsetEnabled = false;
static int32_t m_SunsetTimer = 0;
@ -171,19 +177,14 @@ static void M_CalcRoomVertices(const ROOM_MESH *const mesh, int32_t far_clip)
const double persp = g_FltPersp / zv;
const int32_t depth = zv_int >> W2V_SHIFT;
vbuf->zv += base_z;
if (depth < FOG_END) {
if (depth > FOG_START) {
shade += depth - FOG_START;
}
vbuf->rhw = persp * g_FltRhwOPersp;
} else {
// clip_flags = far_clip;
shade = 0x1FFF;
if (zv >= Output_GetFarZ()) {
vbuf->zv = g_FltFarZ;
vbuf->rhw = persp * g_FltRhwOPersp;
}
shade += Output_CalcFogShade(depth);
vbuf->rhw = persp * g_FltRhwOPersp;
double xs = xv * persp + g_FltWinCenterX;
double ys = yv * persp + g_FltWinCenterY;
@ -408,6 +409,31 @@ static void M_CalcSkyboxLight(const OBJECT_MESH *const mesh)
}
}
void Output_ApplyLevelSettings(void)
{
Output_SetFogStart(Level_GetFogStart() * WALL_L);
Output_SetFogEnd(Level_GetFogEnd() * WALL_L);
Viewport_Reset();
}
void Output_ObserveLevelLoad(void)
{
Output_ApplyLevelSettings();
for (int32_t i = 0; i < COLOR_NUMBER_OF; i++) {
m_NamedColors[i].palette_index =
Output_FindColor8(m_NamedColors[i].rgb);
}
}
void Output_ObserveLevelUnload(void)
{
Output_InitialiseTexturePages(0, true);
Output_InitialiseObjectTextures(0);
if (Output_GetBackgroundType() == BK_OBJECT) {
Output_UnloadBackground();
}
}
void Output_DrawObjectMesh(const OBJECT_MESH *const mesh, const int32_t clip)
{
g_FltWinLeft = 0.0f;
@ -590,8 +616,8 @@ void Output_DrawSprite(
if (flags & SPRF_SHADE) {
const int32_t depth = zv >> W2V_SHIFT;
if (depth > FOG_START) {
shade += depth - FOG_START;
if (depth > Output_GetFogStart()) {
shade += depth - Output_GetFogStart();
if (shade > 0x1FFF) {
return;
}
@ -900,6 +926,26 @@ int32_t Output_GetObjectBounds(const BOUNDS_16 *const bounds)
return 1;
}
int32_t Output_GetNearZ(void)
{
return 20 << W2V_SHIFT;
}
int32_t Output_GetFarZ(void)
{
return Output_GetFogEnd() << W2V_SHIFT;
}
int32_t Output_GetFogEnd(void)
{
return m_FogEnd;
}
void Output_SetFogEnd(const int32_t dist)
{
m_FogEnd = dist;
}
void Output_SetupBelowWater(const bool is_underwater)
{
Render_SetWet(is_underwater);
@ -974,13 +1020,15 @@ void Output_SetLightDivider(const int32_t divider)
int32_t Output_CalcFogShade(const int32_t depth)
{
if (depth > FOG_START) {
return depth - FOG_START;
const int32_t fog_start = Output_GetFogStart();
const int32_t fog_end = Output_GetFogEnd();
if (depth < fog_start) {
return 0;
}
if (depth > FOG_END) {
if (depth >= fog_end) {
return 0x1FFF;
}
return 0;
return (depth - fog_start) * 0x1FFF / (fog_end - fog_start);
}
int32_t Output_GetRoomLightShade(const ROOM_LIGHT_MODE mode)
@ -999,11 +1047,3 @@ void Output_LightRoomVertices(const ROOM *const room)
vtx->light_adder = vtx->light_base + wibble;
}
}
void Output_InitialiseNamedColors(void)
{
for (int32_t i = 0; i < COLOR_NUMBER_OF; i++) {
m_NamedColors[i].palette_index =
Output_FindColor8(m_NamedColors[i].rgb);
}
}

View file

@ -22,6 +22,8 @@ typedef struct {
float g;
} VERTEX_INFO;
void Output_ApplyLevelSettings(void);
void Output_DrawObjectMesh(const OBJECT_MESH *mesh, int32_t clip);
void Output_DrawObjectMesh_I(const OBJECT_MESH *mesh, int32_t clip);
void Output_DrawRoom(const ROOM_MESH *mesh, bool is_outside);
@ -74,7 +76,6 @@ void Output_DrawAirBar(int32_t percent);
BACKGROUND_TYPE Output_GetBackgroundType(void);
void Output_LoadBackgroundFromObject(void);
void Output_InitialiseNamedColors(void);
void Output_InsertShadow(
int16_t radius, const BOUNDS_16 *bounds, const ITEM *item);
@ -87,3 +88,6 @@ void Output_SetShadeEffect(bool shade_effect);
bool Output_IsShadeEffect(void);
void Output_SetSunsetEnabled(bool enabled);
void Output_SetSunsetTimer(int32_t timer);
int32_t Output_GetNearZ(void);
int32_t Output_GetFarZ(void);

View file

@ -384,6 +384,8 @@ static void M_HandleConfigChange(const EVENT *const event, void *const data)
Viewport_AlterFOV(-1);
}
}
Output_ApplyLevelSettings();
}
// TODO: refactor the hell out of me

View file

@ -148,8 +148,8 @@ void Viewport_Reset(void)
vp->width = vp->height * vp->render_ar;
}
vp->near_z = VIEW_NEAR;
vp->far_z = VIEW_FAR;
vp->near_z = Output_GetNearZ() >> W2V_SHIFT;
vp->far_z = Output_GetFarZ() >> W2V_SHIFT;
// We do not update vp->view_angle on purpose, as it's managed by the game
// rather than the window manager. (Think cutscenes, special cameras, etc.)

View file

@ -143,12 +143,6 @@
#define SPRITE_TRANS_QUARTER 0x60000000
#define SPRITE_COLOUR(r, g, b) ((r) | ((g) << 8) | ((b) << 16))
#define VIEW_NEAR (20 * 1) // = 20
#define VIEW_FAR (20 * WALL_L) // = 20480
#define FOG_START (12 * WALL_L) // = 12288
#define FOG_END (20 * WALL_L) // = 20480
#define PITCH_SHIFT 4
#define IDS_DX5_REQUIRED 1