Skip to content

Commit

Permalink
camera: add photo mode
Browse files Browse the repository at this point in the history
This adds a photo mode phase and camera logic to handle movement. No UI
is presented currently.
  • Loading branch information
lahm86 committed Oct 5, 2024
1 parent 2e5a327 commit 708141d
Show file tree
Hide file tree
Showing 12 changed files with 409 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/tr1/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/stable...develop) - ××××-××-××
- added a photo mode feature (#1669)
- added `/sfx` command
- added `/nextlevel` alias to `/endlevel` console command
- added `/quit` alias to `/exit` console command
Expand Down
1 change: 1 addition & 0 deletions docs/tr1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det
- added a flag indicating if new game plus is unlocked to the player config which allows the player to select new game plus or not when making a new game
- added weapons to Lara's empty holsters on pickup
- added options to quiet or mute music while underwater
- added a photo mode feature
- changed weapon pickup behavior when unarmed to set any weapon as the default weapon, not just pistols
- fixed keys and items not working when drawing guns immediately after using them
- fixed counting the secret in The Great Pyramid
Expand Down
5 changes: 5 additions & 0 deletions src/libtrx/include/libtrx/game/math.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ typedef struct __PACKING {
XYZ_16 max;
} BOUNDS_16;

typedef struct __PACKING {
XYZ_32 min;
XYZ_32 max;
} BOUNDS_32;

#elif TR_VERSION == 2
typedef struct __PACKING {
int16_t min_x;
Expand Down
244 changes: 240 additions & 4 deletions src/tr1/game/camera.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
#include "game/items.h"
#include "game/los.h"
#include "game/music.h"
#include "game/phase/phase.h"
#include "game/random.h"
#include "game/room.h"
#include "game/sound.h"
#include "game/viewport.h"
#include "global/const.h"
#include "global/vars.h"
#include "math/math.h"
#include "math/math_misc.h"
#include "math/matrix.h"

#include <libtrx/utils.h>
Expand All @@ -20,17 +22,50 @@
#include <stdbool.h>
#include <stddef.h>

#define DEFAULT_DISTANCE (WALL_L * 3 / 2)
#define PHOTO_ROT_SHIFT (PHD_DEGREE * 4)
#define PHOTO_MAX_PITCH_ROLL (PHD_90 - PHD_DEGREE)
#define PHOTO_MAX_SPEED 100

#define CAM_SPEED_SHIFT(val) (((float)m_PhotoSpeed / PHOTO_MAX_SPEED) * val)
#define CAM_ROT_SHIFT (MAX(PHD_DEGREE, CAM_SPEED_SHIFT(PHOTO_ROT_SHIFT)))

#define SHIFT_X(distance, elevation, angle) \
(((distance * Math_Cos(elevation)) >> W2V_SHIFT) * Math_Sin(angle) \
>> W2V_SHIFT)
#define SHIFT_Z(distance, elevation, angle) \
(((distance * Math_Cos(elevation)) >> W2V_SHIFT) * Math_Cos(angle) \
>> W2V_SHIFT)
#define SHIFT_Y(dy, distance, elevation) \
(dy * -(distance * Math_Sin(elevation) >> W2V_SHIFT))

#define SHIFT_POS(a, b) \
do { \
a.x += b.x; \
a.y += b.y; \
a.z += b.z; \
} while (false)

// Camera speed option ranges from 1-10, so index 0 is unused.
static double m_ManualCameraMultiplier[11] = {
1.0, .5, .625, .75, .875, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0,
};

static bool m_PhotoMode = false;
static int32_t m_PhotoSpeed = 0;
static int16_t m_Roll = 0;
static CAMERA_INFO m_OldCamera = { 0 };
static BOUNDS_32 m_WorldBounds = { 0 };

static void M_Chase(const ITEM *item);
static void M_Combat(const ITEM *item);
static void M_Look(const ITEM *item);
static void M_Fixed(void);
static void M_LoadCutsceneFrame(void);

static void M_UpdatePhotoMode(void);
static void M_ExitPhotoMode(void);

static void M_OffsetAdditionalAngle(int16_t delta);
static void M_OffsetAdditionalElevation(int16_t delta);
static void M_OffsetReset(void);
Expand Down Expand Up @@ -137,7 +172,7 @@ static void M_Look(const ITEM *const item)
item->rot.y + g_Lara.torso_rot.y + g_Lara.head_rot.y;
g_Camera.target_elevation =
item->rot.x + g_Lara.torso_rot.x + g_Lara.head_rot.x;
g_Camera.target_distance = WALL_L * 3 / 2;
g_Camera.target_distance = DEFAULT_DISTANCE;

const int32_t distance =
g_Camera.target_distance * Math_Cos(g_Camera.target_elevation)
Expand Down Expand Up @@ -225,6 +260,196 @@ static void M_LoadCutsceneFrame(void)
Viewport_SetFOV(ref->fov);
}

static void M_UpdatePhotoMode(void)
{
if (!m_PhotoMode) {
m_OldCamera = g_Camera;
m_PhotoMode = true;
}

const bool axis_shift_input = g_Input.camera_up || g_Input.camera_down;
const bool dir_shift_input = g_Input.camera_forward || g_Input.camera_back
|| g_Input.camera_left || g_Input.camera_right;
const bool rot_input =
g_Input.left || g_Input.right || g_Input.forward || g_Input.back;
const bool rot_target_input = g_InputDB.roll;
const bool roll_input = g_Input.step_left || g_Input.step_right;

PHD_ANGLE angles[2];
Math_GetVectorAngles(
g_Camera.target.x - g_Camera.pos.x, g_Camera.target.y - g_Camera.pos.y,
g_Camera.target.z - g_Camera.pos.z, angles);
g_Camera.target_angle = angles[0];
g_Camera.target_elevation = angles[1];
g_Camera.target_distance = DEFAULT_DISTANCE;
g_Camera.target_square = SQUARE(g_Camera.target_distance);

if (g_InputDB.look) {
g_Camera = m_OldCamera;
m_PhotoSpeed = 0;
m_Roll = 0;
} else if (axis_shift_input || dir_shift_input || rot_input || roll_input) {
m_PhotoSpeed++;
CLAMPG(m_PhotoSpeed, PHOTO_MAX_SPEED);
} else {
m_PhotoSpeed = 0;
}

if (g_Input.step_left) {
m_Roll -= CAM_ROT_SHIFT;
} else if (g_Input.step_right) {
m_Roll += CAM_ROT_SHIFT;
}
CLAMP(m_Roll, -PHOTO_MAX_PITCH_ROLL, PHOTO_MAX_PITCH_ROLL);

if (!axis_shift_input && !dir_shift_input && !rot_input
&& !rot_target_input) {
return;
}

if (axis_shift_input) {
const int32_t distance = CAM_SPEED_SHIFT((WALL_L * 5.0) / LOGIC_FPS);
XYZ_32 shift = {
.x = 0,
.y = 0,
.z = 0,
};
if (g_Input.camera_up) {
shift.y = -distance;
} else if (g_Input.camera_down) {
shift.y = distance;
}
SHIFT_POS(g_Camera.pos, shift);
SHIFT_POS(g_Camera.target, shift);
}

if (dir_shift_input) {
const int32_t distance = CAM_SPEED_SHIFT((WALL_L * 5.0) / LOGIC_FPS);

PHD_ANGLE angle = g_Camera.target_angle;
PHD_ANGLE elevation = g_Camera.target_elevation;

if (g_Input.camera_forward || g_Input.camera_back) {
int32_t dy = 0;
if (g_Input.camera_forward) {
dy = 1;
} else {
angle += PHD_180;
dy = -1;
}

const XYZ_32 shift = {
.x = SHIFT_X(distance, elevation, angle),
.z = SHIFT_Z(distance, elevation, angle),
.y = SHIFT_Y(dy, distance, elevation),
};
SHIFT_POS(g_Camera.pos, shift);
SHIFT_POS(g_Camera.target, shift);

Math_GetVectorAngles(
g_Camera.target.x - g_Camera.pos.x,
g_Camera.target.y - g_Camera.pos.y,
g_Camera.target.z - g_Camera.pos.z, angles);
angle = angles[0];
elevation = angles[1];
}

if (g_Input.camera_left || g_Input.camera_right) {
if (g_Input.camera_left) {
angle -= PHD_90;
} else {
angle += PHD_90;
}

const XYZ_32 shift = {
.x = SHIFT_X(distance, elevation, angle),
.z = SHIFT_Z(distance, elevation, angle),
.y = 0,
};
SHIFT_POS(g_Camera.pos, shift);
SHIFT_POS(g_Camera.target, shift);
}
}

if (rot_input) {
if (g_Input.forward) {
g_Camera.target_elevation -= CAM_ROT_SHIFT;
} else if (g_Input.back) {
g_Camera.target_elevation += CAM_ROT_SHIFT;
}
CLAMP(
g_Camera.target_elevation, -PHOTO_MAX_PITCH_ROLL,
PHOTO_MAX_PITCH_ROLL);

if (g_Input.left) {
g_Camera.target_angle -= (int16_t)CAM_ROT_SHIFT;
} else if (g_Input.right) {
g_Camera.target_angle += (int16_t)CAM_ROT_SHIFT;
}
const PHD_ANGLE angle = g_Camera.target_angle;
const PHD_ANGLE elevation = g_Camera.target_elevation;
const int32_t distance = g_Camera.target_distance;
const XYZ_32 shift = {
.x = SHIFT_X(distance, elevation, angle),
.z = SHIFT_Z(distance, elevation, angle),
.y = SHIFT_Y(1, distance, elevation),
};
g_Camera.target.x = g_Camera.pos.x + shift.x;
g_Camera.target.y = g_Camera.pos.y + shift.y;
g_Camera.target.z = g_Camera.pos.z + shift.z;
}

if (rot_target_input) {
const PHD_ANGLE angle = g_Camera.target_angle + PHD_90;
const PHD_ANGLE elevation = g_Camera.target_elevation;
const int32_t distance = g_Camera.target_distance;
const XYZ_32 shift = {
.x = SHIFT_X(distance, elevation, angle),
.z = SHIFT_Z(distance, elevation, angle),
.y = SHIFT_Y(1, distance, elevation),
};
g_Camera.pos.x = g_Camera.target.x - shift.x;
g_Camera.pos.y = g_Camera.target.y - shift.y;
g_Camera.pos.z = g_Camera.target.z - shift.z;
}

// While the camera is free, we want to clamp to within overall world bounds
// to help counteract getting lost in the void.
const GAME_VECTOR cam_pos = g_Camera.pos;
CLAMP(g_Camera.pos.x, m_WorldBounds.min.x, m_WorldBounds.max.x);
CLAMP(g_Camera.pos.y, m_WorldBounds.min.y, m_WorldBounds.max.y);
CLAMP(g_Camera.pos.z, m_WorldBounds.min.z, m_WorldBounds.max.z);

g_Camera.target.x += (g_Camera.pos.x - cam_pos.x);
g_Camera.target.y += (g_Camera.pos.y - cam_pos.y);
g_Camera.target.z += (g_Camera.pos.z - cam_pos.z);

const int16_t pos_room_num =
Room_GetIndexFromPos(g_Camera.pos.x, g_Camera.pos.y, g_Camera.pos.z);
const int16_t tar_room_num = Room_GetIndexFromPos(
g_Camera.target.x, g_Camera.target.y, g_Camera.target.z);

if (pos_room_num != NO_ROOM) {
g_Camera.pos.room_num = pos_room_num;
if (tar_room_num == NO_ROOM) {
g_Camera.target.room_num = pos_room_num;
}
}
if (tar_room_num != NO_ROOM) {
g_Camera.target.room_num = tar_room_num;
if (pos_room_num == NO_ROOM) {
g_Camera.pos.room_num = tar_room_num;
}
}
}

static void M_ExitPhotoMode(void)
{
g_Camera = m_OldCamera;
m_Roll = 0;
m_PhotoMode = false;
}

static void M_OffsetAdditionalAngle(const int16_t delta)
{
g_Camera.additional_angle += delta;
Expand Down Expand Up @@ -621,6 +846,9 @@ static void M_Shift(

void Camera_Initialise(void)
{
m_WorldBounds = Room_GetWorldBounds();
m_PhotoMode = false;
m_Roll = 0;
Camera_ResetPosition();
Camera_Update();
}
Expand All @@ -645,7 +873,7 @@ void Camera_ResetPosition(void)
g_Camera.pos.z = g_Camera.target.z - 100;
g_Camera.pos.room_num = g_Camera.target.room_num;

g_Camera.target_distance = WALL_L * 3 / 2;
g_Camera.target_distance = DEFAULT_DISTANCE;
g_Camera.item = NULL;

g_Camera.type = CAM_CHASE;
Expand All @@ -658,6 +886,13 @@ void Camera_ResetPosition(void)

void Camera_Update(void)
{
if (Phase_Get() == PHASE_PHOTO_MODE) {
M_UpdatePhotoMode();
return;
} else if (m_PhotoMode) {
M_ExitPhotoMode();
}

if (g_Camera.type == CAM_CINEMATIC) {
M_LoadCutsceneFrame();
return;
Expand Down Expand Up @@ -801,7 +1036,7 @@ void Camera_Update(void)
g_Camera.item = NULL;
g_Camera.target_angle = g_Camera.additional_angle;
g_Camera.target_elevation = g_Camera.additional_elevation;
g_Camera.target_distance = WALL_L * 3 / 2;
g_Camera.target_distance = DEFAULT_DISTANCE;
g_Camera.flags = 0;
}

Expand Down Expand Up @@ -892,5 +1127,6 @@ void Camera_Apply(void)
g_Camera.interp.result.pos.x,
g_Camera.interp.result.pos.y + g_Camera.interp.result.shift,
g_Camera.interp.result.pos.z, g_Camera.interp.result.target.x,
g_Camera.interp.result.target.y, g_Camera.interp.result.target.z, 0);
g_Camera.interp.result.target.y, g_Camera.interp.result.target.z,
m_Roll);
}
5 changes: 5 additions & 0 deletions src/tr1/game/phase/phase.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "game/phase/phase_game.h"
#include "game/phase/phase_inventory.h"
#include "game/phase/phase_pause.h"
#include "game/phase/phase_photo_mode.h"
#include "game/phase/phase_picture.h"
#include "game/phase/phase_stats.h"
#include "global/types.h"
Expand Down Expand Up @@ -93,6 +94,10 @@ static void M_SetUnconditionally(const PHASE phase, void *arg)
case PHASE_INVENTORY:
m_Phaser = &g_InventoryPhaser;
break;

case PHASE_PHOTO_MODE:
m_Phaser = &g_PhotoModePhaser;
break;
}

if (m_Phaser && m_Phaser->start) {
Expand Down
1 change: 1 addition & 0 deletions src/tr1/game/phase/phase.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ typedef enum {
PHASE_PICTURE,
PHASE_STATS,
PHASE_INVENTORY,
PHASE_PHOTO_MODE,
} PHASE;

typedef struct {
Expand Down
3 changes: 3 additions & 0 deletions src/tr1/game/phase/phase_game.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ static PHASE_CONTROL M_Control(int32_t nframes)
if (!g_Lara.death_timer && g_InputDB.pause) {
Phase_Set(PHASE_PAUSE, NULL);
return (PHASE_CONTROL) { .end = false };
} else if (g_InputDB.toggle_photo_mode) {
Phase_Set(PHASE_PHOTO_MODE, NULL);
return (PHASE_CONTROL) { .end = false };
} else {
Item_Control();
Effect_Control();
Expand Down
Loading

0 comments on commit 708141d

Please sign in to comment.