tr2/objects/pickup: add 3D pickups drawing

Resolves #1634.
This commit is contained in:
Marcin Kurczewski 2024-11-05 21:40:33 +01:00
parent 2819ea18ea
commit 57cedf1fc8
8 changed files with 213 additions and 95 deletions

View file

@ -7,6 +7,7 @@
- added the ability to skip FMVs with the action key (#1650)
- added the ability to hold forward/back to move through menus more quickly (#1644)
- added optional rendering of pickups in the UI as 3D meshes (#1633)
- added optional rendering of pickups on the ground as 3D meshes (#1634)
- changed the inputs backend from DirectX to SDL (#1695)
- improved controller support to match TR1X
- changed the number of custom layouts to 3

View file

@ -2,9 +2,12 @@
#include "game/items.h"
#include "game/lara/misc.h"
#include "game/matrix.h"
#include "global/funcs.h"
#include "global/vars.h"
#include <libtrx/utils.h>
OBJECT *Object_GetObject(GAME_OBJECT_ID object_id)
{
return &g_Objects[object_id];
@ -45,3 +48,83 @@ void __cdecl Object_Collision_Trap(
Object_Collision(item_num, lara_item, coll);
}
}
BOUNDS_16 Object_GetBoundingBox(
const OBJECT *const obj, const FRAME_INFO *const frame)
{
int16_t **mesh_ptrs = &g_Meshes[obj->mesh_idx];
int32_t *bone = &g_AnimBones[obj->bone_idx];
const int16_t *mesh_rots = frame->mesh_rots;
Matrix_PushUnit();
Matrix_TranslateRel(frame->offset.x, frame->offset.y, frame->offset.z);
Matrix_RotYXZsuperpack(&mesh_rots, 0);
BOUNDS_16 new_bounds = {
.min_x = 0x7FFF,
.min_y = 0x7FFF,
.min_z = 0x7FFF,
.max_x = -0x7FFF,
.max_y = -0x7FFF,
.max_z = -0x7FFF,
};
for (int32_t mesh_idx = 0; mesh_idx < obj->mesh_count; mesh_idx++) {
if (mesh_idx != 0) {
int32_t bone_extra_flags = *bone;
if (bone_extra_flags & BF_MATRIX_POP) {
Matrix_Pop();
}
if (bone_extra_flags & BF_MATRIX_PUSH) {
Matrix_Push();
}
Matrix_TranslateRel(bone[1], bone[2], bone[3]);
Matrix_RotYXZsuperpack(&mesh_rots, 0);
bone += 4;
}
const int16_t *obj_ptr = mesh_ptrs[mesh_idx];
obj_ptr += 5;
const int32_t vtx_count = *obj_ptr++;
for (int32_t i = 0; i < vtx_count; i++) {
// clang-format off
const MATRIX *const mptr = g_MatrixPtr;
const double xv = (
mptr->_00 * obj_ptr[0] +
mptr->_01 * obj_ptr[1] +
mptr->_02 * obj_ptr[2] +
mptr->_03
);
const double yv = (
mptr->_10 * obj_ptr[0] +
mptr->_11 * obj_ptr[1] +
mptr->_12 * obj_ptr[2] +
mptr->_13
);
double zv = (
mptr->_20 * obj_ptr[0] +
mptr->_21 * obj_ptr[1] +
mptr->_22 * obj_ptr[2] +
mptr->_23
);
// clang-format on
const int32_t x = ((int32_t)xv) >> W2V_SHIFT;
const int32_t y = ((int32_t)yv) >> W2V_SHIFT;
const int32_t z = ((int32_t)zv) >> W2V_SHIFT;
new_bounds.min_x = MIN(new_bounds.min_x, x);
new_bounds.min_y = MIN(new_bounds.min_y, y);
new_bounds.min_z = MIN(new_bounds.min_z, z);
new_bounds.max_x = MAX(new_bounds.max_x, x);
new_bounds.max_y = MAX(new_bounds.max_y, y);
new_bounds.max_z = MAX(new_bounds.max_z, z);
obj_ptr += 3;
}
}
Matrix_Pop();
return new_bounds;
}

View file

@ -11,3 +11,5 @@ void __cdecl Object_Collision(
void __cdecl Object_Collision_Trap(
int16_t item_num, ITEM *lara_item, COLL_INFO *coll);
BOUNDS_16 Object_GetBoundingBox(const OBJECT *obj, const FRAME_INFO *frame);

View file

@ -1,13 +1,19 @@
#include "game/objects/general/pickup.h"
#include "config.h"
#include "game/gameflow.h"
#include "game/gun/gun.h"
#include "game/input.h"
#include "game/inventory/backpack.h"
#include "game/inventory/common.h"
#include "game/items.h"
#include "game/lara/control.h"
#include "game/lara/misc.h"
#include "game/matrix.h"
#include "game/objects/common.h"
#include "game/output.h"
#include "game/overlay.h"
#include "game/room.h"
#include "global/funcs.h"
#include "global/vars.h"
@ -66,8 +72,7 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item)
item->rot.z = 0;
if (!Item_TestPosition(g_PickupBounds, item, lara_item)) {
item->rot = old_rot;
return;
goto cleanup;
}
if (lara_item->current_anim_state == LS_PICKUP) {
@ -75,7 +80,7 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item)
== g_Anims[LA_PICKUP].frame_base + LF_PICKUP_ERASE) {
M_DoPickup(item_num);
}
return;
goto cleanup;
}
if (lara_item->current_anim_state == LS_FLARE_PICKUP) {
@ -85,7 +90,7 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item)
&& g_Lara.gun_type != LGT_FLARE) {
M_DoFlarePickup(item_num);
}
return;
goto cleanup;
}
if (g_Input.action && !lara_item->gravity
@ -108,9 +113,10 @@ static void M_DoAboveWater(const int16_t item_num, ITEM *const lara_item)
lara_item->goal_anim_state = LS_STOP;
g_Lara.gun_status = LGS_HANDS_BUSY;
}
return;
goto cleanup;
}
cleanup:
item->rot = old_rot;
}
@ -124,8 +130,7 @@ static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item)
item->rot.z = 0;
if (!Item_TestPosition(g_PickupBoundsUW, item, lara_item)) {
item->rot = old_rot;
return;
goto cleanup;
}
if (lara_item->current_anim_state == LS_PICKUP) {
@ -133,7 +138,7 @@ static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item)
== g_Anims[LA_UNDERWATER_PICKUP].frame_base + LF_PICKUP_UW) {
M_DoPickup(item_num);
}
return;
goto cleanup;
}
if (lara_item->current_anim_state == LS_FLARE_PICKUP) {
@ -145,14 +150,14 @@ static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item)
M_DoFlarePickup(item_num);
Flare_DrawMeshes();
}
return;
goto cleanup;
}
if (g_Input.action && lara_item->current_anim_state == LS_TREAD
&& g_Lara.gun_status == LGS_ARMLESS
&& (g_Lara.gun_type != LGT_FLARE || item->object_id != O_FLARE_ITEM)) {
if (!Lara_MovePosition(&g_PickupPositionUW, item, lara_item)) {
return;
goto cleanup;
}
if (item->object_id == O_FLARE_ITEM) {
@ -168,9 +173,10 @@ static void M_DoUnderwater(const int16_t item_num, ITEM *const lara_item)
} while (lara_item->current_anim_state != LS_PICKUP);
lara_item->goal_anim_state = LS_TREAD;
}
return;
goto cleanup;
}
cleanup:
item->rot = old_rot;
}
@ -179,11 +185,107 @@ void Pickup_Setup(OBJECT *const obj)
// TODO: change this to Pickup_Collision after we decompile
// both comparisons in ExtractSaveGameInfo() and GetCarriedItems()
obj->collision = (void *)0x00437E70;
obj->draw_routine = Object_DrawSpriteItem;
obj->draw_routine = Pickup_Draw;
obj->save_position = 1;
obj->save_flags = 1;
}
void Pickup_Draw(const ITEM *const item)
{
if (!g_Config.visuals.enable_3d_pickups) {
Object_DrawSpriteItem(item);
return;
}
if (!g_Objects[item->object_id].loaded) {
Object_DrawSpriteItem(item);
return;
}
// Convert item to menu display item.
const GAME_OBJECT_ID inv_object_id = Inv_GetItemOption(item->object_id);
const OBJECT *const obj = Object_GetObject(inv_object_id);
if (!obj->loaded || obj->mesh_count < 0) {
Object_DrawSpriteItem(item);
return;
}
// Get the first frame of the first animation, and its bounding box.
const FRAME_INFO *frame = (const FRAME_INFO *)obj->frame_base;
const BOUNDS_16 bounds = Object_GetBoundingBox(obj, frame);
// First - Is there floor under the item?
// This is mostly true, but for example the 4 items in the Obelisk of
// Khamoon the 4 items are sitting on top of a static mesh which is not
// floor.
int16_t room_num = item->room_num;
const SECTOR *const sector =
Room_GetSector(item->pos.x, item->pos.y, item->pos.z, &room_num);
const int16_t floor_height =
Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z);
int16_t offset;
if (item->pos.y == floor_height) {
// Is the floor "just below" the item? Take the position from the anim.
offset =
floor_height - frame->offset.y - (bounds.max_y - bounds.min_y) / 2;
} else {
// Otherwise leave it as-is.
offset = item->pos.y;
}
Matrix_Push();
Matrix_TranslateAbs(item->pos.x, offset, item->pos.z);
Matrix_RotYXZ(item->rot.y, item->rot.x, item->rot.z);
S_CalculateLight(item->pos.x, item->pos.y, item->pos.z, item->room_num);
const int32_t clip = S_GetObjectBounds(&frame->bounds);
if (clip) {
// From this point on the function is a slightly customised version
// of the code in DrawAnimatingItem starting with the line that
// matches the following line.
int32_t bit = 1;
int16_t **meshpp = &g_Meshes[obj->mesh_idx];
int32_t *bone = &g_AnimBones[obj->bone_idx];
Matrix_TranslateRel(frame->offset.x, frame->offset.y, frame->offset.z);
const int16_t *mesh_rots = frame->mesh_rots;
Matrix_RotYXZsuperpack(&mesh_rots, 0);
if (item->mesh_bits & bit) {
Output_InsertPolygons(*meshpp++, clip);
}
for (int i = 1; i < obj->mesh_count; i++) {
int32_t bone_extra_flags = *bone;
if (bone_extra_flags & BF_MATRIX_POP) {
Matrix_Pop();
}
if (bone_extra_flags & BF_MATRIX_PUSH) {
Matrix_Push();
}
Matrix_TranslateRel(bone[1], bone[2], bone[3]);
Matrix_RotYXZsuperpack(&mesh_rots, 0);
// Extra rotation is ignored in this case as it's not needed.
bit <<= 1;
if (item->mesh_bits & bit) {
Output_InsertPolygons(*meshpp, clip);
}
bone += 4;
meshpp++;
}
}
Matrix_Pop();
}
void __cdecl Pickup_Collision(
const int16_t item_num, ITEM *const lara_item, COLL_INFO *const coll)
{

View file

@ -4,6 +4,8 @@
void Pickup_Setup(OBJECT *obj);
void Pickup_Draw(const ITEM *item);
void __cdecl Pickup_Collision(
int16_t item_num, ITEM *lara_item, COLL_INFO *coll);

View file

@ -6,6 +6,7 @@
#include "game/inventory/common.h"
#include "game/matrix.h"
#include "game/music.h"
#include "game/objects/common.h"
#include "game/output.h"
#include "game/text.h"
#include "game/viewport.h"
@ -71,88 +72,6 @@ static float M_Ease(const int32_t cur_frame, const int32_t max_frames)
return result;
}
static BOUNDS_16 M_GetBounds(
const OBJECT *const obj, const FRAME_INFO *const frame)
{
int16_t **mesh_ptrs = &g_Meshes[obj->mesh_idx];
int32_t *bone = &g_AnimBones[obj->bone_idx];
const int16_t *mesh_rots = frame->mesh_rots;
Matrix_PushUnit();
Matrix_TranslateRel(frame->offset.x, frame->offset.y, frame->offset.z);
Matrix_RotYXZsuperpack(&mesh_rots, 0);
BOUNDS_16 new_bounds = {
.min_x = 0x7FFF,
.min_y = 0x7FFF,
.min_z = 0x7FFF,
.max_x = -0x7FFF,
.max_y = -0x7FFF,
.max_z = -0x7FFF,
};
for (int32_t mesh_idx = 0; mesh_idx < obj->mesh_count; mesh_idx++) {
if (mesh_idx != 0) {
int32_t bone_extra_flags = *bone;
if (bone_extra_flags & BF_MATRIX_POP) {
Matrix_Pop();
}
if (bone_extra_flags & BF_MATRIX_PUSH) {
Matrix_Push();
}
Matrix_TranslateRel(bone[1], bone[2], bone[3]);
Matrix_RotYXZsuperpack(&mesh_rots, 0);
bone += 4;
}
const int16_t *obj_ptr = mesh_ptrs[mesh_idx];
obj_ptr += 5;
const int32_t vtx_count = *obj_ptr++;
for (int32_t i = 0; i < vtx_count; i++) {
PHD_VBUF *const vbuf = &g_PhdVBuf[i];
// clang-format off
const MATRIX *const mptr = g_MatrixPtr;
const double xv = (
mptr->_00 * obj_ptr[0] +
mptr->_01 * obj_ptr[1] +
mptr->_02 * obj_ptr[2] +
mptr->_03
);
const double yv = (
mptr->_10 * obj_ptr[0] +
mptr->_11 * obj_ptr[1] +
mptr->_12 * obj_ptr[2] +
mptr->_13
);
double zv = (
mptr->_20 * obj_ptr[0] +
mptr->_21 * obj_ptr[1] +
mptr->_22 * obj_ptr[2] +
mptr->_23
);
// clang-format on
const int32_t x = ((int32_t)xv) >> W2V_SHIFT;
const int32_t y = ((int32_t)yv) >> W2V_SHIFT;
const int32_t z = ((int32_t)zv) >> W2V_SHIFT;
new_bounds.min_x = MIN(new_bounds.min_x, x);
new_bounds.min_y = MIN(new_bounds.min_y, y);
new_bounds.min_z = MIN(new_bounds.min_z, z);
new_bounds.max_x = MAX(new_bounds.max_x, x);
new_bounds.max_y = MAX(new_bounds.max_y, y);
new_bounds.max_z = MAX(new_bounds.max_z, z);
obj_ptr += 3;
}
}
Matrix_Pop();
return new_bounds;
}
bool __cdecl Overlay_FlashCounter(const int32_t ticks)
{
if (m_FlashCounter > 0) {
@ -457,7 +376,7 @@ static void M_DrawPickup3D(const DISPLAY_PICKUP *const pickup)
if (frame->bounds.min_x == frame->bounds.max_x
&& frame->bounds.min_y == frame->bounds.max_y) {
// fix broken collision box for the prayer wheel
bounds = M_GetBounds(obj, frame);
bounds = Object_GetBoundingBox(obj, frame);
}
const int32_t scale = 1280;

View file

@ -13,6 +13,10 @@
}
},
"Properties": {
"enable_3d_pickups": {
"Title": "3D pickups",
"Description": "Enables 3D models to be rendered in place of the sprites for pickup items."
},
"enable_cheats": {
"Title": "Cheats",
"Description": "Enables various cheats:\n- L: immediately end the level.\n- I: give Lara all weapons; a boost of ammo and medipacks; and all plot items for the current level.\n- O: enable DOZY cheat (swimming midair).\n - WALK key: exit DOZY.\n - GUN key: open the closest door (doesn't work in certain places)."

View file

@ -46,6 +46,11 @@
"ID": "graphics",
"Image": "Graphics/graphic5.jpg",
"Properties": [
{
"Field": "enable_3d_pickups",
"DataType": "Bool",
"DefaultValue": true
},
{
"Field": "screenshot_format",
"DataType": "Enum",