mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 20:58:07 +03:00
output: allow aspect-ratio specific images
This commit is contained in:
parent
fbc26fcba6
commit
daf6dc3020
10 changed files with 250 additions and 32 deletions
|
@ -690,7 +690,18 @@ default game flow for examples.
|
||||||
</td>
|
</td>
|
||||||
<td><code>path</code></td>
|
<td><code>path</code></td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td> Displays the specified picture for a fixed time. </td>
|
<td>
|
||||||
|
Displays the specified picture for a fixed time.
|
||||||
|
Files that are needed to function only with a specific aspect ratio can
|
||||||
|
be placed in a directory adjacent to the main image, named according to
|
||||||
|
the aspect ratio – for example, 4x3/title.png or 16x10/title.png. The
|
||||||
|
game won't attempt to match these precisely; instead, it will select the
|
||||||
|
file with the aspect ratio closest to the game's viewport. The main image
|
||||||
|
designated by <code>path</code> is presumed to have a 16:9 aspect ratio
|
||||||
|
for this purpose, and as such there's no need for 16x9-specific
|
||||||
|
directory.<br/>
|
||||||
|
This logic applies to all images.
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr valign="top">
|
<tr valign="top">
|
||||||
<td><code>display_time</code></td>
|
<td><code>display_time</code></td>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
- added an ability to customize the water color [see the reference](/docs/GAME_FLOW.md#water-color-table) (#1532)
|
- added an ability to customize the water color [see the reference](/docs/GAME_FLOW.md#water-color-table) (#1532)
|
||||||
- added support for a hex water color notation (eg. `#80FFFF`) in the game flow file
|
- added support for a hex water color notation (eg. `#80FFFF`) in the game flow file
|
||||||
- added support for antitriggers, like TR2+ (#2580)
|
- added support for antitriggers, like TR2+ (#2580)
|
||||||
|
- added support for aspect ratio-specific images (#1840)
|
||||||
- changed the `draw_distance_min` and `draw_distance_max` to `fog_start` and `fog_end`
|
- changed the `draw_distance_min` and `draw_distance_max` to `fog_start` and `fog_end`
|
||||||
- changed `Select Detail` dialog title to `Graphic Options`
|
- changed `Select Detail` dialog title to `Graphic Options`
|
||||||
- changed the number of static mesh slots from 50 to 256 (#2734)
|
- changed the number of static mesh slots from 50 to 256 (#2734)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
- added the ability for spike walls to be reset (antitriggered)
|
- added the ability for spike walls to be reset (antitriggered)
|
||||||
- added the current music track and timestamp to the savegame so they now persist on load (#2579)
|
- added the current music track and timestamp to the savegame so they now persist on load (#2579)
|
||||||
- added waterfalls to the savegame so that they now persist on load (#2686)
|
- added waterfalls to the savegame so that they now persist on load (#2686)
|
||||||
|
- added support for aspect ratio-specific images (#1840)
|
||||||
- changed savegame files to be stored in the `saves` directory (#2087)
|
- changed savegame files to be stored in the `saves` directory (#2087)
|
||||||
- changed the default fog distance to 22 tiles cutting off at 30 tiles to match TR1X (#1622)
|
- changed the default fog distance to 22 tiles cutting off at 30 tiles to match TR1X (#1622)
|
||||||
- changed the number of static mesh slots from 50 to 256 (#2734)
|
- changed the number of static mesh slots from 50 to 256 (#2734)
|
||||||
|
|
209
src/libtrx/game/output/background.c
Normal file
209
src/libtrx/game/output/background.c
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
#include "game/output/background.h"
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "filesystem.h"
|
||||||
|
#include "game/viewport.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "strings.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "vector.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define M_RELATIVE_ERROR(a, b) ABS((a) - (b)) / (b)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *file_name;
|
||||||
|
float diff;
|
||||||
|
} M_CANDIDATE;
|
||||||
|
|
||||||
|
static char *m_LastPath = nullptr;
|
||||||
|
|
||||||
|
static IMAGE *M_CreateImageFromPath(const char *path);
|
||||||
|
static float M_GetScreenAspectRatio(void);
|
||||||
|
static int M_CompareCandidates(const void *a, const void *b);
|
||||||
|
static VECTOR *M_ScanCandidates(const char *path, size_t *dir_len_out);
|
||||||
|
static bool M_TryLoadCandidates(
|
||||||
|
const char *path, VECTOR *candidates, size_t dir_len);
|
||||||
|
static void M_FreeCandidates(VECTOR *candidates);
|
||||||
|
|
||||||
|
static IMAGE *M_CreateImageFromPath(const char *const path)
|
||||||
|
{
|
||||||
|
if (TR_VERSION == 1) {
|
||||||
|
return Image_CreateFromFileInto(
|
||||||
|
path, Viewport_GetWidth(), Viewport_GetHeight(), IMAGE_FIT_SMART);
|
||||||
|
} else {
|
||||||
|
return Image_CreateFromFile(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static float M_GetScreenAspectRatio(void)
|
||||||
|
{
|
||||||
|
return Viewport_GetWidth() / (float)Viewport_GetHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int M_CompareCandidates(const void *const a, const void *const b)
|
||||||
|
{
|
||||||
|
const M_CANDIDATE *const c1 = a;
|
||||||
|
const M_CANDIDATE *const c2 = b;
|
||||||
|
if (c1->diff < c2->diff) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (c1->diff > c2->diff) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VECTOR *M_ScanCandidates(
|
||||||
|
const char *const path, size_t *const dir_len_out)
|
||||||
|
{
|
||||||
|
VECTOR *candidates = nullptr;
|
||||||
|
|
||||||
|
const char *last_slash = strrchr(path, '/');
|
||||||
|
const char *last_backslash = strrchr(path, '\\');
|
||||||
|
const char *last_sep =
|
||||||
|
last_slash > last_backslash ? last_slash : last_backslash;
|
||||||
|
size_t dir_len;
|
||||||
|
char *dir_path;
|
||||||
|
if (last_sep != nullptr) {
|
||||||
|
dir_len = last_sep - path;
|
||||||
|
dir_path = String_Format("%.*s", (int)dir_len, path);
|
||||||
|
} else {
|
||||||
|
dir_len = 0;
|
||||||
|
dir_path = Memory_DupStr(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *file_name = last_sep ? last_sep + 1 : path;
|
||||||
|
const char *ext_ptr = strrchr(file_name, '.');
|
||||||
|
const float screen_ratio = M_GetScreenAspectRatio();
|
||||||
|
|
||||||
|
void *const dir_handle = File_OpenDirectory(dir_path);
|
||||||
|
if (dir_handle == nullptr) {
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates = Vector_Create(sizeof(M_CANDIDATE));
|
||||||
|
const char *entry;
|
||||||
|
while ((entry = File_ReadDirectory(dir_handle)) != nullptr) {
|
||||||
|
// Match the file itself, and assume it's of 16:9 aspect ratio.
|
||||||
|
if (String_Equivalent(entry, file_name)) {
|
||||||
|
const float ratio = 16.0f / 9.0f;
|
||||||
|
Vector_Add(
|
||||||
|
candidates,
|
||||||
|
&(M_CANDIDATE) {
|
||||||
|
.file_name = Memory_DupStr(file_name),
|
||||||
|
.diff = M_RELATIVE_ERROR(ratio, screen_ratio),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match directories with pattern: <width>x<height>
|
||||||
|
int32_t w = 0, h = 0;
|
||||||
|
if (sscanf(entry, "%dx%d", &w, &h) == 2) {
|
||||||
|
const float ratio = w / (float)h;
|
||||||
|
Vector_Add(
|
||||||
|
candidates,
|
||||||
|
&(M_CANDIDATE) {
|
||||||
|
.file_name = String_Format("%s/%s", entry, file_name),
|
||||||
|
.diff = M_RELATIVE_ERROR(ratio, screen_ratio),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File_CloseDirectory(dir_handle);
|
||||||
|
|
||||||
|
finish:
|
||||||
|
*dir_len_out = dir_len;
|
||||||
|
Memory_FreePointer(&dir_path);
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool M_TryLoadCandidates(
|
||||||
|
const char *const path, VECTOR *const candidates, const size_t dir_len)
|
||||||
|
{
|
||||||
|
for (int32_t i = 0; i < candidates->count; i++) {
|
||||||
|
const M_CANDIDATE *candidate = Vector_Get(candidates, i);
|
||||||
|
char *full_path;
|
||||||
|
if (dir_len > 0) {
|
||||||
|
full_path = String_Format(
|
||||||
|
"%.*s/%s", (int32_t)dir_len, path, candidate->file_name);
|
||||||
|
} else {
|
||||||
|
full_path = String_Format("%s", candidate->file_name);
|
||||||
|
}
|
||||||
|
IMAGE *const image = M_CreateImageFromPath(full_path);
|
||||||
|
Memory_FreePointer(&full_path);
|
||||||
|
if (image != nullptr) {
|
||||||
|
Output_LoadBackgroundFromImage(image);
|
||||||
|
Image_Free(image);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void M_FreeCandidates(VECTOR *const candidates)
|
||||||
|
{
|
||||||
|
if (candidates == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int32_t i = 0; i < candidates->count; i++) {
|
||||||
|
M_CANDIDATE *const candidate = Vector_Get(candidates, i);
|
||||||
|
Memory_Free(candidate->file_name);
|
||||||
|
}
|
||||||
|
Vector_Free(candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Output_LoadBackgroundFromFile(const char *const path)
|
||||||
|
{
|
||||||
|
LOG_INFO("Loading image %s", path);
|
||||||
|
size_t dir_len = 0;
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// Try aspect-ratio specific directories.
|
||||||
|
VECTOR *candidates = M_ScanCandidates(path, &dir_len);
|
||||||
|
if (candidates != nullptr) {
|
||||||
|
M_CANDIDATE *raw_candidates = Vector_GetData(candidates);
|
||||||
|
qsort(
|
||||||
|
raw_candidates, candidates->count, sizeof(M_CANDIDATE),
|
||||||
|
M_CompareCandidates);
|
||||||
|
for (int32_t i = 0; i < candidates->count; i++) {
|
||||||
|
const M_CANDIDATE *const candidate = Vector_Get(candidates, i);
|
||||||
|
LOG_INFO(
|
||||||
|
"Found candidate %s (diff=%.02f)", candidate->file_name,
|
||||||
|
candidate->diff);
|
||||||
|
}
|
||||||
|
if (M_TryLoadCandidates(path, candidates, dir_len)) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
// Fallback to the main image.
|
||||||
|
IMAGE *const image = M_CreateImageFromPath(path);
|
||||||
|
if (image != nullptr) {
|
||||||
|
result = true;
|
||||||
|
Output_LoadBackgroundFromImage(image);
|
||||||
|
Image_Free(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
M_FreeCandidates(candidates);
|
||||||
|
if (result) {
|
||||||
|
char *prev = m_LastPath;
|
||||||
|
m_LastPath = Memory_DupStr(path);
|
||||||
|
Memory_FreePointer(&prev);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *Output_GetLastBackgroundPath(void)
|
||||||
|
{
|
||||||
|
return m_LastPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Output_ClearLastBackgroundPath(void)
|
||||||
|
{
|
||||||
|
Memory_FreePointer(&m_LastPath);
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "./output/background.h"
|
||||||
#include "./output/common.h"
|
#include "./output/common.h"
|
||||||
#include "./output/const.h"
|
#include "./output/const.h"
|
||||||
#include "./output/draw.h"
|
#include "./output/draw.h"
|
||||||
|
|
15
src/libtrx/include/libtrx/game/output/background.h
Normal file
15
src/libtrx/include/libtrx/game/output/background.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../engine/image.h"
|
||||||
|
|
||||||
|
bool Output_LoadBackgroundFromFile(const char *path);
|
||||||
|
|
||||||
|
extern bool Output_LoadBackgroundFromImage(const IMAGE *image);
|
||||||
|
extern void Output_LoadBackgroundFromObject(void);
|
||||||
|
extern void Output_UnloadBackground(void);
|
||||||
|
|
||||||
|
extern void Output_DrawBackground(void);
|
||||||
|
|
||||||
|
// TODO: make these functions private once output module is consolidated
|
||||||
|
char *Output_GetLastBackgroundPath(void);
|
||||||
|
void Output_ClearLastBackgroundPath(void);
|
|
@ -10,12 +10,7 @@ extern bool Output_MakeScreenshot(const char *path);
|
||||||
extern void Output_BeginScene(void);
|
extern void Output_BeginScene(void);
|
||||||
extern void Output_EndScene(void);
|
extern void Output_EndScene(void);
|
||||||
|
|
||||||
extern bool Output_LoadBackgroundFromFile(const char *file_name);
|
|
||||||
extern void Output_LoadBackgroundFromObject(void);
|
|
||||||
extern void Output_UnloadBackground(void);
|
|
||||||
|
|
||||||
extern void Output_DrawBlackRectangle(int32_t opacity);
|
extern void Output_DrawBlackRectangle(int32_t opacity);
|
||||||
extern void Output_DrawBackground(void);
|
|
||||||
extern void Output_DrawPolyList(void);
|
extern void Output_DrawPolyList(void);
|
||||||
|
|
||||||
extern void Output_SetupBelowWater(bool is_underwater);
|
extern void Output_SetupBelowWater(bool is_underwater);
|
||||||
|
|
|
@ -181,6 +181,7 @@ sources = [
|
||||||
'game/objects/names.c',
|
'game/objects/names.c',
|
||||||
'game/objects/traps/movable_block.c',
|
'game/objects/traps/movable_block.c',
|
||||||
'game/objects/vars.c',
|
'game/objects/vars.c',
|
||||||
|
'game/output/background.c',
|
||||||
'game/output/common.c',
|
'game/output/common.c',
|
||||||
'game/output/textures.c',
|
'game/output/textures.c',
|
||||||
'game/packer.c',
|
'game/packer.c',
|
||||||
|
|
|
@ -63,7 +63,6 @@ static int32_t m_SurfaceHeight = 0;
|
||||||
static GFX_2D_SURFACE *m_PictureSurface = nullptr;
|
static GFX_2D_SURFACE *m_PictureSurface = nullptr;
|
||||||
static GFX_2D_SURFACE *m_TextureSurfaces[GFX_MAX_TEXTURES] = { nullptr };
|
static GFX_2D_SURFACE *m_TextureSurfaces[GFX_MAX_TEXTURES] = { nullptr };
|
||||||
|
|
||||||
static char *m_BackdropImagePath = nullptr;
|
|
||||||
static const char *m_ImageExtensions[] = {
|
static const char *m_ImageExtensions[] = {
|
||||||
".png", ".jpg", ".jpeg", ".pcx", nullptr,
|
".png", ".jpg", ".jpeg", ".pcx", nullptr,
|
||||||
};
|
};
|
||||||
|
@ -318,7 +317,6 @@ static void M_DrawLightningSegment(const LIGHTNING *const lightning)
|
||||||
vertices[vtx_idx].b = color.b; \
|
vertices[vtx_idx].b = color.b; \
|
||||||
vertices[vtx_idx].a = 128.0f;
|
vertices[vtx_idx].a = 128.0f;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
LOG_INFO("%d %d %d, %d %d %d, %d", p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, t1);
|
|
||||||
SET(0, p0.x, p0.y, p0.z, blue);
|
SET(0, p0.x, p0.y, p0.z, blue);
|
||||||
SET(1, p0.x + t1 / 2, p0.y, p0.z, white);
|
SET(1, p0.x + t1 / 2, p0.y, p0.z, white);
|
||||||
SET(2, p1.x + t2 / 2, p1.y, p1.z, white);
|
SET(2, p1.x + t2 / 2, p1.y, p1.z, white);
|
||||||
|
@ -424,7 +422,7 @@ void Output_Shutdown(void)
|
||||||
m_Renderer3D = nullptr;
|
m_Renderer3D = nullptr;
|
||||||
}
|
}
|
||||||
GFX_Context_Detach();
|
GFX_Context_Detach();
|
||||||
Memory_FreePointer(&m_BackdropImagePath);
|
Output_ClearLastBackgroundPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Output_SetWindowSize(int32_t width, int32_t height)
|
void Output_SetWindowSize(int32_t width, int32_t height)
|
||||||
|
@ -467,8 +465,9 @@ void Output_ApplyRenderSettings(void)
|
||||||
GFX_3D_Renderer_SetAnisotropyFilter(
|
GFX_3D_Renderer_SetAnisotropyFilter(
|
||||||
m_Renderer3D, g_Config.rendering.anisotropy_filter);
|
m_Renderer3D, g_Config.rendering.anisotropy_filter);
|
||||||
|
|
||||||
if (m_BackdropImagePath != nullptr) {
|
const char *const last_path = Output_GetLastBackgroundPath();
|
||||||
Output_LoadBackgroundFromFile(m_BackdropImagePath);
|
if (last_path != nullptr) {
|
||||||
|
Output_LoadBackgroundFromFile(last_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,21 +780,9 @@ void Output_DrawUISprite(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Output_LoadBackgroundFromFile(const char *const path)
|
bool Output_LoadBackgroundFromImage(const IMAGE *const image)
|
||||||
{
|
{
|
||||||
ASSERT(path != nullptr);
|
M_DownloadBackdropSurface(image);
|
||||||
const char *old_path = m_BackdropImagePath;
|
|
||||||
m_BackdropImagePath = File_GuessExtension(path, m_ImageExtensions);
|
|
||||||
Memory_FreePointer(&old_path);
|
|
||||||
|
|
||||||
IMAGE *const img = Image_CreateFromFileInto(
|
|
||||||
m_BackdropImagePath, Viewport_GetWidth(), Viewport_GetHeight(),
|
|
||||||
IMAGE_FIT_SMART);
|
|
||||||
if (img == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
M_DownloadBackdropSurface(img);
|
|
||||||
Image_Free(img);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -808,7 +795,7 @@ void Output_LoadBackgroundFromObject(void)
|
||||||
void Output_UnloadBackground(void)
|
void Output_UnloadBackground(void)
|
||||||
{
|
{
|
||||||
M_DownloadBackdropSurface(nullptr);
|
M_DownloadBackdropSurface(nullptr);
|
||||||
Memory_FreePointer(&m_BackdropImagePath);
|
Output_ClearLastBackgroundPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Output_DrawLightningSegment(
|
void Output_DrawLightningSegment(
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <libtrx/game/scaler.h>
|
#include <libtrx/game/scaler.h>
|
||||||
#include <libtrx/log.h>
|
#include <libtrx/log.h>
|
||||||
#include <libtrx/memory.h>
|
#include <libtrx/memory.h>
|
||||||
|
#include <libtrx/strings.h>
|
||||||
#include <libtrx/utils.h>
|
#include <libtrx/utils.h>
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -738,14 +739,9 @@ BACKGROUND_TYPE Output_GetBackgroundType(void)
|
||||||
return m_BackgroundType;
|
return m_BackgroundType;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Output_LoadBackgroundFromFile(const char *const file_name)
|
bool Output_LoadBackgroundFromImage(const IMAGE *const image)
|
||||||
{
|
{
|
||||||
IMAGE *const image = Image_CreateFromFile(file_name);
|
|
||||||
if (image == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Render_LoadBackgroundFromImage(image);
|
Render_LoadBackgroundFromImage(image);
|
||||||
Image_Free(image);
|
|
||||||
m_BackgroundType = BK_IMAGE;
|
m_BackgroundType = BK_IMAGE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -774,6 +770,7 @@ void Output_UnloadBackground(void)
|
||||||
{
|
{
|
||||||
Render_UnloadBackground();
|
Render_UnloadBackground();
|
||||||
m_BackgroundType = BK_TRANSPARENT;
|
m_BackgroundType = BK_TRANSPARENT;
|
||||||
|
Output_ClearLastBackgroundPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Output_InsertBackPolygon(
|
void Output_InsertBackPolygon(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue