Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support external palettes in injection files #1521

Merged
merged 3 commits into from
Sep 14, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- improved crash debug information on Windows
- improved vertex movement when looking through water portals (#1493)
- improved anisotropic filter rendering (#902, #1507)
- improved skybox appearance (#1520)

## [4.3](https://github.com/LostArtefacts/TR1X/compare/4.2...4.3) - 2024-08-15
- added deadly water feature from TR2+ for custom levels (#1404)
Expand Down
109 changes: 66 additions & 43 deletions src/game/inject.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,29 +113,30 @@ static INJECTION_INFO *m_Aggregate = NULL;

static void Inject_LoadFromFile(INJECTION *injection, const char *filename);

static uint8_t Inject_RemapRGB(RGB_888 rgb);
static uint16_t Inject_RemapRGB(LEVEL_INFO *level_info, RGB_888 rgb);
static void Inject_AlignTextureReferences(
OBJECT_INFO *object, uint8_t *palette_map, int32_t page_base);
OBJECT_INFO *object, uint16_t *palette_map, int32_t page_base);

static void Inject_LoadTexturePages(
INJECTION *injection, uint8_t *palette_map, uint8_t *page_ptr);
INJECTION *injection, LEVEL_INFO *level_info, uint16_t *palette_map,
RGBA_8888 *page_ptr);
static void Inject_TextureData(
INJECTION *injection, LEVEL_INFO *level_info, int32_t page_base);
static void Inject_MeshData(INJECTION *injection, LEVEL_INFO *level_info);
static void Inject_AnimData(INJECTION *injection, LEVEL_INFO *level_info);
static void Inject_AnimRangeEdits(INJECTION *injection);
static void Inject_ObjectData(
INJECTION *injection, LEVEL_INFO *level_info, uint8_t *palette_map);
INJECTION *injection, LEVEL_INFO *level_info, uint16_t *palette_map);
static void Inject_SFXData(INJECTION *injection, LEVEL_INFO *level_info);

static int16_t *Inject_GetMeshTexture(FACE_EDIT *face_edit);

static void Inject_ApplyFaceEdit(
FACE_EDIT *face_edit, int16_t *data_ptr, int16_t texture);
static void Inject_ApplyMeshEdit(MESH_EDIT *mesh_edit, uint8_t *palette_map);
static void Inject_MeshEdits(INJECTION *injection, uint8_t *palette_map);
static void Inject_ApplyMeshEdit(MESH_EDIT *mesh_edit, uint16_t *palette_map);
static void Inject_MeshEdits(INJECTION *injection, uint16_t *palette_map);
static void Inject_TextureOverwrites(
INJECTION *injection, LEVEL_INFO *level_info, uint8_t *palette_map);
INJECTION *injection, LEVEL_INFO *level_info, uint16_t *palette_map);

static void Inject_FloorDataEdits(INJECTION *injection, LEVEL_INFO *level_info);
static void Inject_TriggerParameterChange(
Expand Down Expand Up @@ -382,9 +383,9 @@ void Inject_AllInjections(LEVEL_INFO *level_info)

BENCHMARK *const benchmark = Benchmark_Start();

uint8_t palette_map[256];
uint8_t *source_pages =
Memory_Alloc(m_Aggregate->texture_page_count * PAGE_SIZE);
uint16_t palette_map[256];
RGBA_8888 *source_pages = Memory_Alloc(
m_Aggregate->texture_page_count * PAGE_SIZE * sizeof(RGBA_8888));
int32_t source_page_count = 0;
int32_t tpage_base = level_info->texture_page_count;

Expand All @@ -395,7 +396,7 @@ void Inject_AllInjections(LEVEL_INFO *level_info)
}

Inject_LoadTexturePages(
injection, palette_map,
injection, level_info, palette_map,
source_pages + (source_page_count * PAGE_SIZE));

Inject_TextureData(injection, level_info, tpage_base);
Expand Down Expand Up @@ -433,13 +434,13 @@ void Inject_AllInjections(LEVEL_INFO *level_info)
data->level_page_count = level_info->texture_page_count;
data->source_page_count = source_page_count;
data->source_pages = source_pages;
data->level_pages = level_info->texture_page_ptrs;
data->level_pages = level_info->texture_rgb_page_ptrs;
data->object_count = level_info->texture_count;
data->sprite_count = level_info->sprite_info_count;

if (Packer_Pack(data)) {
level_info->texture_page_count += Packer_GetAddedPageCount();
level_info->texture_page_ptrs = data->level_pages;
level_info->texture_rgb_page_ptrs = data->level_pages;
}

Memory_FreePointer(&source_pages);
Expand All @@ -450,7 +451,8 @@ void Inject_AllInjections(LEVEL_INFO *level_info)
}

static void Inject_LoadTexturePages(
INJECTION *injection, uint8_t *palette_map, uint8_t *page_ptr)
INJECTION *injection, LEVEL_INFO *level_info, uint16_t *palette_map,
RGBA_8888 *page_ptr)
{
BENCHMARK *const benchmark = Benchmark_Start();

Expand All @@ -468,17 +470,31 @@ static void Inject_LoadTexturePages(
source_palette[i].r *= 4;
source_palette[i].g *= 4;
source_palette[i].b *= 4;

palette_map[i] = Inject_RemapRGB(source_palette[i]);
}
for (int32_t i = 0; i < 256; i++) {
palette_map[i] = Inject_RemapRGB(level_info, source_palette[i]);
}

// Read in each page for this injection and realign the pixels
// to the level's palette.
const size_t pixel_count = PAGE_SIZE * inj_info->texture_page_count;
VFile_Read(fp, page_ptr, pixel_count);
for (size_t i = 0; i < pixel_count; i++, page_ptr++) {
*page_ptr = palette_map[*page_ptr];
uint8_t *indices = Memory_Alloc(pixel_count);
VFile_Read(fp, indices, pixel_count);
uint8_t *input = indices;
RGBA_8888 *output = page_ptr;
for (size_t i = 0; i < pixel_count; i++) {
const uint8_t index = *input++;
if (index == 0) {
output->a = 0;
} else {
output->r = source_palette[index].r;
output->g = source_palette[index].g;
output->b = source_palette[index].b;
output->a = 255;
}
output++;
}
Memory_FreePointer(&indices);

Benchmark_End(benchmark, NULL);
}
Expand Down Expand Up @@ -736,7 +752,7 @@ static void Inject_AnimRangeEdits(INJECTION *injection)
}

static void Inject_ObjectData(
INJECTION *injection, LEVEL_INFO *level_info, uint8_t *palette_map)
INJECTION *injection, LEVEL_INFO *level_info, uint16_t *palette_map)
{
BENCHMARK *const benchmark = Benchmark_Start();

Expand Down Expand Up @@ -829,7 +845,7 @@ static void Inject_SFXData(INJECTION *injection, LEVEL_INFO *level_info)
}

static void Inject_AlignTextureReferences(
OBJECT_INFO *object, uint8_t *palette_map, int32_t page_base)
OBJECT_INFO *object, uint16_t *palette_map, int32_t page_base)
{
int16_t **mesh = &g_Meshes[object->mesh_index];
for (int32_t i = 0; i < object->nmeshes; i++) {
Expand Down Expand Up @@ -872,28 +888,25 @@ static void Inject_AlignTextureReferences(
}
}

static uint8_t Inject_RemapRGB(RGB_888 rgb)
static uint16_t Inject_RemapRGB(LEVEL_INFO *level_info, RGB_888 rgb)
{
// Find the index of the nearest match to the given RGB
int32_t best_match = 0x7fffffff;
int32_t test_match;
int32_t best_index = 0;
for (int32_t i = 1; i < 256; i++) {
const RGB_888 test_rgb = Output_GetPaletteColor(i);
const int32_t r = rgb.r - test_rgb.r;
const int32_t g = rgb.g - test_rgb.g;
const int32_t b = rgb.b - test_rgb.b;
test_match = SQUARE(r) + SQUARE(g) + SQUARE(b);

if (test_match < best_match) {
best_match = test_match;
best_index = i;
// Find the index of the exact match to the given RGB
for (int32_t i = 0; i < level_info->palette_size; i++) {
const RGB_888 test_rgb = level_info->palette[i];
if (rgb.r == test_rgb.r && rgb.g == test_rgb.g && rgb.b == test_rgb.b) {
return i;
}
}
return best_index;

// Match not found - expand the game palette
level_info->palette_size++;
level_info->palette = Memory_Realloc(
level_info->palette, level_info->palette_size * sizeof(RGB_888));
level_info->palette[level_info->palette_size - 1] = rgb;
return level_info->palette_size - 1;
}

static void Inject_MeshEdits(INJECTION *injection, uint8_t *palette_map)
static void Inject_MeshEdits(INJECTION *injection, uint16_t *palette_map)
{
INJECTION_INFO *inj_info = injection->info;
VFILE *const fp = injection->fp;
Expand Down Expand Up @@ -963,7 +976,7 @@ static void Inject_MeshEdits(INJECTION *injection, uint8_t *palette_map)
Benchmark_End(benchmark, NULL);
}

static void Inject_ApplyMeshEdit(MESH_EDIT *mesh_edit, uint8_t *palette_map)
static void Inject_ApplyMeshEdit(MESH_EDIT *mesh_edit, uint16_t *palette_map)
{
OBJECT_INFO object = g_Objects[mesh_edit->object_id];
if (!object.loaded) {
Expand Down Expand Up @@ -1115,7 +1128,7 @@ static int16_t *Inject_GetMeshTexture(FACE_EDIT *face_edit)
}

static void Inject_TextureOverwrites(
INJECTION *injection, LEVEL_INFO *level_info, uint8_t *palette_map)
INJECTION *injection, LEVEL_INFO *level_info, uint16_t *palette_map)
{
BENCHMARK *const benchmark = Benchmark_Start();

Expand All @@ -1133,13 +1146,23 @@ static void Inject_TextureOverwrites(
VFile_Read(fp, source_img, source_width * source_height);

// Copy the source image pixels directly into the target page.
uint8_t *page = level_info->texture_page_ptrs + target_page * PAGE_SIZE;
RGBA_8888 *page =
level_info->texture_rgb_page_ptrs + target_page * PAGE_SIZE;
for (int32_t y = 0; y < source_height; y++) {
for (int32_t x = 0; x < source_width; x++) {
const int32_t pal_idx = source_img[y * source_width + x];
const uint8_t pal_idx = source_img[y * source_width + x];
const int32_t target_pixel =
(y + target_y) * PAGE_WIDTH + x + target_x;
*(page + target_pixel) = palette_map[pal_idx];
if (pal_idx == 0) {
(*(page + target_pixel)).a = 0;
} else {
const RGB_888 pix =
level_info->palette[palette_map[pal_idx]];
(*(page + target_pixel)).r = pix.r;
(*(page + target_pixel)).g = pix.g;
(*(page + target_pixel)).b = pix.b;
(*(page + target_pixel)).a = 255;
}
}
}

Expand Down
67 changes: 48 additions & 19 deletions src/game/level.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ static void Level_LoadTexturePages(VFILE *file)
BENCHMARK *const benchmark = Benchmark_Start();
m_LevelInfo.texture_page_count = VFile_ReadS32(file);
LOG_INFO("%d texture pages", m_LevelInfo.texture_page_count);
m_LevelInfo.texture_page_ptrs =
m_LevelInfo.texture_palette_page_ptrs =
Memory_Alloc(m_LevelInfo.texture_page_count * PAGE_SIZE);
VFile_Read(
file, m_LevelInfo.texture_page_ptrs,
file, m_LevelInfo.texture_palette_page_ptrs,
PAGE_SIZE * m_LevelInfo.texture_page_count);
Benchmark_End(benchmark, NULL);
}
Expand Down Expand Up @@ -781,21 +781,22 @@ static void Level_LoadPalette(VFILE *file)
{
BENCHMARK *const benchmark = Benchmark_Start();
LOG_INFO("");
RGB_888 palette[256];
m_LevelInfo.palette_size = 256;
m_LevelInfo.palette =
Memory_Alloc(sizeof(RGB_888) * m_LevelInfo.palette_size);
for (int32_t i = 0; i < 256; i++) {
palette[i].r = VFile_ReadU8(file);
palette[i].g = VFile_ReadU8(file);
palette[i].b = VFile_ReadU8(file);
m_LevelInfo.palette[i].r = VFile_ReadU8(file);
m_LevelInfo.palette[i].g = VFile_ReadU8(file);
m_LevelInfo.palette[i].b = VFile_ReadU8(file);
}
palette[0].r = 0;
palette[0].g = 0;
palette[0].b = 0;
m_LevelInfo.palette[0].r = 0;
m_LevelInfo.palette[0].g = 0;
m_LevelInfo.palette[0].b = 0;
for (int i = 1; i < 256; i++) {
palette[i].r *= 4;
palette[i].g *= 4;
palette[i].b *= 4;
m_LevelInfo.palette[i].r *= 4;
m_LevelInfo.palette[i].g *= 4;
m_LevelInfo.palette[i].b *= 4;
}
Output_SetPalette(palette);
Benchmark_End(benchmark, NULL);
}

Expand Down Expand Up @@ -897,6 +898,30 @@ static void Level_CompleteSetup(int32_t level_num)
Room_ParseFloorData(m_LevelInfo.floor_data);
Memory_FreePointer(&m_LevelInfo.floor_data);

// Expand paletted texture data to RGB
m_LevelInfo.texture_rgb_page_ptrs = Memory_Alloc(
m_LevelInfo.texture_page_count * PAGE_SIZE * sizeof(RGBA_8888));
RGBA_8888 *output = m_LevelInfo.texture_rgb_page_ptrs;
const uint8_t *input = m_LevelInfo.texture_palette_page_ptrs;
for (int32_t i = 0; i < m_LevelInfo.texture_page_count; i++) {
for (int32_t j = 0; j < PAGE_SIZE; j++) {
const uint8_t index = *input++;
if (index == 0) {
output->r = 0;
output->g = 0;
output->b = 0;
output->a = 0;
} else {
RGB_888 pix = m_LevelInfo.palette[index];
output->r = pix.r;
output->g = pix.g;
output->b = pix.b;
output->a = 255;
}
output++;
}
}

Inject_AllInjections(&m_LevelInfo);

Level_MarkWaterEdgeVertices();
Expand All @@ -921,14 +946,17 @@ static void Level_CompleteSetup(int32_t level_num)
Output_ReserveVertexBuffer(max_vertices);

// Move the prepared texture pages into g_TexturePagePtrs.
uint8_t *base = GameBuf_Alloc(
m_LevelInfo.texture_page_count * PAGE_SIZE, GBUF_TEXTURE_PAGES);
RGBA_8888 *final_texture_data = GameBuf_Alloc(
m_LevelInfo.texture_page_count * PAGE_SIZE * sizeof(RGBA_8888),
GBUF_TEXTURE_PAGES);
memcpy(
final_texture_data, m_LevelInfo.texture_rgb_page_ptrs,
m_LevelInfo.texture_page_count * PAGE_SIZE * sizeof(RGBA_8888));
for (int i = 0; i < m_LevelInfo.texture_page_count; i++) {
g_TexturePagePtrs[i] = base;
memcpy(base, m_LevelInfo.texture_page_ptrs + i * PAGE_SIZE, PAGE_SIZE);
base += PAGE_SIZE;
g_TexturePagePtrs[i] = &final_texture_data[i * PAGE_SIZE];
}
Output_DownloadTextures(m_LevelInfo.texture_page_count);
Output_SetPalette(m_LevelInfo.palette, m_LevelInfo.palette_size);

// Initialise the sound effects.
size_t *sample_sizes =
Expand Down Expand Up @@ -1021,7 +1049,8 @@ void Level_Load(int level_num)
BENCHMARK *const benchmark = Benchmark_Start();

// clean previous level data
Memory_FreePointer(&m_LevelInfo.texture_page_ptrs);
Memory_FreePointer(&m_LevelInfo.texture_palette_page_ptrs);
Memory_FreePointer(&m_LevelInfo.texture_rgb_page_ptrs);
Memory_FreePointer(&m_LevelInfo.anim_frame_offsets);
Memory_FreePointer(&m_LevelInfo.sample_offsets);
Memory_FreePointer(&m_InjectionInfo);
Expand Down
27 changes: 17 additions & 10 deletions src/game/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ static int m_OverlayCurAlpha = 0;
static int m_OverlayDstAlpha = 0;
static int m_BackdropCurAlpha = 0;
static int m_BackdropDstAlpha = 0;
static RGB_888 *m_ColorPalette = NULL;

static int32_t m_WibbleOffset = 0;
static double m_WibbleOffsetDbl = 0.0;
Expand Down Expand Up @@ -530,6 +531,7 @@ void Output_Shutdown(void)
{
S_Output_Shutdown();
Memory_FreePointer(&m_BackdropImagePath);
Memory_FreePointer(&m_ColorPalette);
}

void Output_ReserveVertexBuffer(const size_t size)
Expand Down Expand Up @@ -562,16 +564,6 @@ RGBA_8888 Output_RGB2RGBA(const RGB_888 color)
return ret;
}

void Output_SetPalette(RGB_888 palette[256])
{
S_Output_SetPalette(palette);
}

RGB_888 Output_GetPaletteColor(uint8_t idx)
{
return S_Output_GetPaletteColor(idx);
}

void Output_DrawBlack(void)
{
Output_DrawBlackOverlay(255);
Expand Down Expand Up @@ -1398,3 +1390,18 @@ int Output_GetObjectBounds(const BOUNDS_16 *const bounds)

return 1; // fully on screen
}

void Output_SetPalette(const RGB_888 *palette, const size_t palette_size)
{
m_ColorPalette =
Memory_Realloc(m_ColorPalette, sizeof(RGB_888) * palette_size);
memcpy(m_ColorPalette, palette, sizeof(RGB_888) * palette_size);
}

RGB_888 Output_GetPaletteColor(uint16_t idx)
{
if (m_ColorPalette == NULL) {
return (RGB_888) { 0 };
}
return m_ColorPalette[idx];
}
Loading
Loading