items: handle null anims/frames for sprite objects
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / Build TR1 (push) Has been skipped
Publish a pre-release / Build TR2 (push) Has been skipped
Publish a pre-release / Create a prerelease (push) Has been skipped

This ensures that sprite objects (or anything with no animation index
defined) are assigned a default animation and as such allows us to
handle null frames when getting item bounds. When Lara's animations are
injected, animation index 0 (which sprite objects would previously
default to) is no longer valid, hence sprites then pointing to a null
entry.
This commit is contained in:
lahm86 2025-04-08 09:47:25 +01:00
parent 6820532711
commit 2fb3824b3b
8 changed files with 59 additions and 36 deletions

View file

@ -6,6 +6,7 @@
- fixed mesh faces not being drawn under some circumstances (#2452, #2438)
- fixed objects disappearing too early around screen edges (#2005)
- fixed the trapezoid filter being toggled if Alt-F4 (either left or right) is used to close the game (#2690)
- fixed a crash when 3D pickups are disabled and Lara crosses a trigger to look at a pickup item (#2711, regression from 4.8)
- fixed trapezoid filter warping on faces close to the camera (#2629, regression from 4.9)
- fixed Mac builds crashing upon start (regression from 4.9)
- fixed sprites rendering black if no shade value is assigned in the level (#2701, regression from 4.9)

View file

@ -7,6 +7,7 @@ static ANIM *m_Anims = nullptr;
static ANIM_CHANGE *m_Changes = nullptr;
static ANIM_RANGE *m_Ranges = nullptr;
static ANIM_BONE *m_Bones = nullptr;
static ANIM m_NullAnim = {};
void Anim_InitialiseAnims(const int32_t num_anims)
{
@ -37,7 +38,7 @@ int32_t Anim_GetTotalCount(void)
ANIM *Anim_GetAnim(const int32_t anim_idx)
{
return &m_Anims[anim_idx];
return anim_idx == NO_ANIM ? &m_NullAnim : &m_Anims[anim_idx];
}
ANIM_CHANGE *Anim_GetChange(const int32_t change_idx)

View file

@ -201,7 +201,7 @@ void Anim_LoadFrames(const int16_t *data, const int32_t data_length)
// so ensure everything that's loaded is configured as such.
for (int32_t i = 0; i < O_NUMBER_OF; i++) {
OBJECT *const obj = Object_Get(i);
if (obj->loaded && obj->mesh_count >= 0 && obj->anim_idx == -1
if (obj->loaded && obj->mesh_count >= 0 && obj->anim_idx == NO_ANIM
&& obj->frame_base == nullptr) {
obj->frame_base = M_FindFrameBase(obj->frame_ofs);
}

View file

@ -301,7 +301,11 @@ void Item_SwitchToObjAnim(
const GAME_OBJECT_ID obj_id)
{
const OBJECT *const obj = Object_Get(obj_id);
item->anim_num = obj->anim_idx + anim_idx;
if (obj->anim_idx == NO_ANIM) {
item->anim_num = NO_ANIM;
} else {
item->anim_num = obj->anim_idx + anim_idx;
}
const ANIM *const anim = Item_GetAnim(item);
if (frame < 0) {

View file

@ -934,6 +934,7 @@ void Level_ReadSpriteSequences(VFILE *const file)
OBJECT *const obj = Object_Get(object_id);
obj->mesh_count = num_meshes;
obj->mesh_idx = mesh_idx;
obj->anim_idx = NO_ANIM;
obj->loaded = true;
} else if (object_id - O_NUMBER_OF < MAX_STATIC_OBJECTS) {
STATIC_OBJECT_2D *const obj =

View file

@ -2,6 +2,8 @@
#include "./types.h"
#define NO_ANIM (-1)
void Anim_InitialiseAnims(int32_t num_anims);
void Anim_InitialiseChanges(int32_t num_changes);
void Anim_InitialiseRanges(int32_t num_ranges);

View file

@ -28,6 +28,7 @@
} \
} while (0)
static BOUNDS_16 m_NullBounds = {};
static BOUNDS_16 m_InterpolatedBounds = {};
void Item_Control(void)
@ -411,28 +412,27 @@ bool Item_IsTriggerActive(ITEM *item)
ANIM_FRAME *Item_GetBestFrame(const ITEM *item)
{
ANIM_FRAME *frmptr[2];
ANIM_FRAME *frames[2];
int32_t rate;
int32_t frac = Item_GetFrames(item, frmptr, &rate);
if (frac <= rate / 2) {
return frmptr[0];
} else {
return frmptr[1];
}
const int32_t frac = Item_GetFrames(item, frames, &rate);
return frames[(frac > rate / 2) ? 1 : 0];
}
const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item)
{
int32_t rate;
ANIM_FRAME *frmptr[2];
int32_t frac = Item_GetFrames(item, frmptr, &rate);
if (!frac) {
return &frmptr[0]->bounds;
ANIM_FRAME *frames[2];
const int32_t frac = Item_GetFrames(item, frames, &rate);
if (frames[0] == nullptr) {
return &m_NullBounds;
}
const BOUNDS_16 *const a = &frmptr[0]->bounds;
const BOUNDS_16 *const b = &frmptr[1]->bounds;
if (frac == 0) {
return &frames[0]->bounds;
}
const BOUNDS_16 *const a = &frames[0]->bounds;
const BOUNDS_16 *const b = &frames[1]->bounds;
BOUNDS_16 *const result = &m_InterpolatedBounds;
result->min.x = a->min.x + (((b->min.x - a->min.x) * frac) / rate);
@ -444,9 +444,13 @@ const BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *item)
return result;
}
int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate)
int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate)
{
const ANIM *const anim = Item_GetAnim(item);
if (anim->frame_ptr == nullptr) {
frames[0] = nullptr;
return 0;
}
const int32_t cur_frame_num = item->frame_num - anim->frame_base;
const int32_t last_frame_num = anim->frame_end - anim->frame_base;
@ -454,8 +458,8 @@ int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate)
const int32_t first_key_frame_num = cur_frame_num / key_frame_span;
const int32_t second_key_frame_num = first_key_frame_num + 1;
frmptr[0] = &anim->frame_ptr[first_key_frame_num];
frmptr[1] = &anim->frame_ptr[second_key_frame_num];
frames[0] = &anim->frame_ptr[first_key_frame_num];
frames[1] = &anim->frame_ptr[second_key_frame_num];
const int32_t key_frame_shift = cur_frame_num % key_frame_span;
const int32_t numerator = key_frame_shift;

View file

@ -17,6 +17,7 @@
#include <libtrx/game/matrix.h>
#include <libtrx/utils.h>
static BOUNDS_16 m_NullBounds = {};
static BOUNDS_16 m_InterpolatedBounds = {};
static OBJECT_BOUNDS M_ConvertBounds(const int16_t *bounds_in);
@ -330,9 +331,14 @@ int32_t Item_IsTriggerActive(ITEM *const item)
return ok;
}
int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate)
int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frames[], int32_t *rate)
{
const ANIM *const anim = Item_GetAnim(item);
if (anim->frame_ptr == nullptr) {
frames[0] = nullptr;
return 0;
}
const int32_t cur_frame_num = item->frame_num - anim->frame_base;
const int32_t last_frame_num = anim->frame_end - anim->frame_base;
const int32_t key_frame_span = anim->interpolation;
@ -351,8 +357,8 @@ int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate)
}
}
frmptr[0] = &anim->frame_ptr[first_key_frame_num];
frmptr[1] = &anim->frame_ptr[second_key_frame_num];
frames[0] = &anim->frame_ptr[first_key_frame_num];
frames[1] = &anim->frame_ptr[second_key_frame_num];
// OG
if (g_Config.rendering.fps == 30) {
@ -386,31 +392,35 @@ int32_t Item_GetFrames(const ITEM *item, ANIM_FRAME *frmptr[], int32_t *rate)
BOUNDS_16 *Item_GetBoundsAccurate(const ITEM *const item)
{
int32_t rate;
ANIM_FRAME *frmptr[2];
const int32_t frac = Item_GetFrames(item, frmptr, &rate);
if (!frac) {
return &frmptr[0]->bounds;
ANIM_FRAME *frames[2];
const int32_t frac = Item_GetFrames(item, frames, &rate);
if (frames[0] == nullptr) {
return &m_NullBounds;
}
if (frac == 0) {
return &frames[0]->bounds;
}
#define CALC(target, b1, b2, prop) \
target->prop = (b1)->prop + ((((b2)->prop - (b1)->prop) * frac) / rate);
BOUNDS_16 *const result = &m_InterpolatedBounds;
CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min.x);
CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max.x);
CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min.y);
CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max.y);
CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, min.z);
CALC(result, &frmptr[0]->bounds, &frmptr[1]->bounds, max.z);
CALC(result, &frames[0]->bounds, &frames[1]->bounds, min.x);
CALC(result, &frames[0]->bounds, &frames[1]->bounds, max.x);
CALC(result, &frames[0]->bounds, &frames[1]->bounds, min.y);
CALC(result, &frames[0]->bounds, &frames[1]->bounds, max.y);
CALC(result, &frames[0]->bounds, &frames[1]->bounds, min.z);
CALC(result, &frames[0]->bounds, &frames[1]->bounds, max.z);
return result;
}
ANIM_FRAME *Item_GetBestFrame(const ITEM *const item)
{
ANIM_FRAME *frmptr[2];
ANIM_FRAME *frames[2];
int32_t rate;
const int32_t frac = Item_GetFrames(item, frmptr, &rate);
return frmptr[(frac > rate / 2) ? 1 : 0];
const int32_t frac = Item_GetFrames(item, frames, &rate);
return frames[(frac > rate / 2) ? 1 : 0];
}
bool Item_IsNearItem(