Super Mario 64 OpenGL port for PC. Mirror of https://github.com/sm64pc/sm64pc https://github.com/sm64pc/sm64pc
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

11627 lines
394 KiB

#include <ultra64.h>
#define INCLUDED_FROM_CAMERA_C
#include "sm64.h"
#include "camera.h"
#include "seq_ids.h"
#include "dialog_ids.h"
#include "audio/external.h"
#include "mario_misc.h"
#include "game_init.h"
#include "hud.h"
#include "engine/math_util.h"
#include "area.h"
#include "engine/surface_collision.h"
#include "engine/behavior_script.h"
#include "level_update.h"
#include "ingame_menu.h"
#include "mario_actions_cutscene.h"
#include "save_file.h"
#include "object_helpers.h"
#include "print.h"
#include "spawn_sound.h"
#include "behavior_actions.h"
#include "behavior_data.h"
#include "object_list_processor.h"
#include "paintings.h"
#include "engine/graph_node.h"
#include "level_table.h"
#define CBUTTON_MASK (U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS)
/**
* @file camera.c
* Implements the camera system, including C-button input, camera modes, camera triggers, and cutscenes.
*
* When working with the camera, you should be familiar with sm64's coordinate system.
* Relative to the camera, the coordinate system follows the right hand rule:
* +X points right.
* +Y points up.
* +Z points out of the screen.
*
* You should also be familiar with Euler angles: 'pitch', 'yaw', and 'roll'.
* pitch: rotation about the X-axis, measured from +Y.
* Unlike yaw and roll, pitch is bounded in +-0x4000 (90 degrees).
* Pitch is 0 when the camera points parallel to the xz-plane (+Y points straight up).
*
* yaw: rotation about the Y-axis, measured from (absolute) +Z.
* Positive yaw rotates clockwise, towards +X.
*
* roll: rotation about the Z-axis, measured from the camera's right direction.
* Unfortunately, it's weird: For some reason, roll is flipped. Positive roll makes the camera
* rotate counterclockwise, which means the WORLD rotates clockwise. Luckily roll is rarely
* used.
*
* Remember the right hand rule: make a thumbs-up with your right hand, stick your thumb in the
* +direction (except for roll), and the angle follows the rotation of your curled fingers.
*
* Illustrations:
* Following the right hand rule, each hidden axis's positive direction points out of the screen.
*
* YZ-Plane (pitch) XZ-Plane (yaw) XY-Plane (roll -- Note flipped)
* +Y -Z +Y
* ^ ^ (into the ^
* --|-- | screen) |<-
* +pitch / | \ -pitch | | \ -roll
* v | v | | |
* +Z <------O------> -Z -X <------O------> +X -X <------O------> +X
* | ^ | ^ | |
* | \ | / | / +roll
* | -yaw --|-- +yaw |<-
* v v v
* -Y +Z -Y
*
*/
// BSS
/**
* Stores lakitu's position from the last frame, used for transitioning in next_lakitu_state()
*/
Vec3f sOldPosition;
/**
* Stores lakitu's focus from the last frame, used for transitioning in next_lakitu_state()
*/
Vec3f sOldFocus;
/**
* Global array of PlayerCameraState.
* L is real.
*/
struct PlayerCameraState gPlayerCameraState[2];
/**
* Direction controlled by player 2, moves the focus during the credits.
*/
Vec3f sPlayer2FocusOffset;
/**
* The pitch used for the credits easter egg.
*/
s16 sCreditsPlayer2Pitch;
/**
* The yaw used for the credits easter egg.
*/
s16 sCreditsPlayer2Yaw;
/**
* Used to decide when to zoom out in the pause menu.
*/
u8 sFramesPaused;
#ifndef VERSION_EU
extern struct CameraFOVStatus sFOVState;
extern struct TransitionInfo sModeTransition;
extern struct PlayerGeometry sMarioGeometry;
extern s16 unusedFreeRoamWallYaw;
extern s16 sAvoidYawVel;
extern s16 sCameraYawAfterDoorCutscene;
extern s16 unusedSplinePitch;
extern s16 unusedSplineYaw;
extern struct HandheldShakePoint sHandheldShakeSpline[4];
extern s16 sHandheldShakeMag;
extern f32 sHandheldShakeTimer;
extern f32 sHandheldShakeInc;
extern s16 sHandheldShakePitch;
extern s16 sHandheldShakeYaw;
extern s16 sHandheldShakeRoll;
extern u32 unused8033B30C;
extern u32 unused8033B310;
extern s16 sSelectionFlags;
extern s16 unused8033B316;
extern s16 s2ndRotateFlags;
extern s16 unused8033B31A;
extern s16 sCameraSoundFlags;
extern u16 sCButtonsPressed;
extern s16 sCutsceneDialogID;
extern struct LakituState gLakituState;
extern s16 unused8033B3E8;
extern s16 sAreaYaw;
extern s16 sAreaYawChange;
extern s16 sLakituDist;
extern s16 sLakituPitch;
extern f32 sZoomAmount;
extern s16 sCSideButtonYaw;
extern s16 sBehindMarioSoundTimer;
extern f32 sZeroZoomDist;
extern s16 sCUpCameraPitch;
extern s16 sModeOffsetYaw;
extern s16 sSpiralStairsYawOffset;
extern s16 s8DirModeBaseYaw;
extern s16 s8DirModeYawOffset;
extern f32 sPanDistance;
extern f32 sCannonYOffset;
extern struct ModeTransitionInfo sModeInfo;
extern Vec3f sCastleEntranceOffset;
extern u32 sParTrackIndex;
extern struct ParallelTrackingPoint *sParTrackPath;
extern struct CameraStoredInfo sParTrackTransOff;
extern struct CameraStoredInfo sCameraStoreCUp;
extern struct CameraStoredInfo sCameraStoreCutscene;
extern s16 gCameraMovementFlags;
extern s16 sStatusFlags;
extern struct CutsceneSplinePoint sCurCreditsSplinePos[32];
extern struct CutsceneSplinePoint sCurCreditsSplineFocus[32];
extern s16 sCutsceneSplineSegment;
extern f32 sCutsceneSplineSegmentProgress;
extern s16 unused8033B6E8;
extern s16 sCutsceneShot;
extern s16 gCutsceneTimer;
extern struct CutsceneVariable sCutsceneVars[10];
extern s32 gObjCutsceneDone;
extern u32 gCutsceneObjSpawn;
extern struct Camera *gCamera;
#endif
/**
* Lakitu's position and focus.
* @see LakituState
*/
struct LakituState gLakituState;
struct CameraFOVStatus sFOVState;
struct TransitionInfo sModeTransition;
struct PlayerGeometry sMarioGeometry;
struct Camera *gCamera;
s16 unusedFreeRoamWallYaw;
s16 sAvoidYawVel;
s16 sCameraYawAfterDoorCutscene;
/**
* The current spline that controls the camera's position during the credits.
*/
struct CutsceneSplinePoint sCurCreditsSplinePos[32];
/**
* The current spline that controls the camera's focus during the credits.
*/
struct CutsceneSplinePoint sCurCreditsSplineFocus[32];
s16 unusedSplinePitch;
s16 unusedSplineYaw;
/**
* The progress (from 0 to 1) through the current spline segment.
* When it becomes >= 1, 1.0 is subtracted from it and sCutsceneSplineSegment is increased.
*/
f32 sCutsceneSplineSegmentProgress;
/**
* The current segment of the CutsceneSplinePoint[] being used.
*/
s16 sCutsceneSplineSegment;
s16 unused8033B6E8;
// Shaky Hand-held Camera effect variables
struct HandheldShakePoint sHandheldShakeSpline[4];
s16 sHandheldShakeMag;
f32 sHandheldShakeTimer;
f32 sHandheldShakeInc;
s16 sHandheldShakePitch;
s16 sHandheldShakeYaw;
s16 sHandheldShakeRoll;
/**
* Controls which object to spawn in the intro and ending cutscenes.
*/
u32 gCutsceneObjSpawn;
/**
* Controls when an object-based cutscene should end. It's only used in the star spawn cutscenes, but
* yoshi also toggles this.
*/
s32 gObjCutsceneDone;
u32 unused8033B30C;
u32 unused8033B310;
/**
* Determines which R-Trigger mode is selected in the pause menu.
*/
s16 sSelectionFlags;
/**
* Flags that determine what movements the camera should start / do this frame.
*/
s16 gCameraMovementFlags;
s16 unused8033B316;
/**
* Flags that change how modes operate and how lakitu moves.
* The most commonly used flag is CAM_FLAG_SMOOTH_MOVEMENT, which makes lakitu fly to the next position,
* instead of warping.
*/
s16 sStatusFlags;
/**
* Flags that determine whether the player has already rotated left or right. Used in radial mode to
* determine whether to rotate all the way, or just to 60 degrees.
*/
s16 s2ndRotateFlags;
s16 unused8033B31A;
/**
* Flags that control buzzes and sounds that play, mostly for C-button input.
*/
s16 sCameraSoundFlags;
/**
* Stores what C-Buttons are pressed this frame.
*/
u16 sCButtonsPressed;
/**
* A copy of gDialogID, the dialog displayed during the cutscene.
*/
s16 sCutsceneDialogID;
/**
* The currently playing shot in the cutscene.
*/
s16 sCutsceneShot;
/**
* The current frame of the cutscene shot.
*/
s16 gCutsceneTimer;
s16 unused8033B3E8;
#ifdef VERSION_EU
s16 unused8033B3E82;
#endif
/**
* The angle of the direction vector from the area's center to mario's position.
*/
s16 sAreaYaw;
/**
* How much sAreaYaw changed when mario moved.
*/
s16 sAreaYawChange;
/**
* Lakitu's distance from mario in C-Down mode
*/
s16 sLakituDist;
/**
* How much lakitu looks down in C-Down mode
*/
s16 sLakituPitch;
/**
* The amount of distance left to zoom out
*/
f32 sZoomAmount;
s16 sCSideButtonYaw;
/**
* Sound timer used to space out sounds in behind mario mode
*/
s16 sBehindMarioSoundTimer;
/**
* Virtually unused aside being set to 0 and compared with gCameraZoomDist (which is never < 0)
*/
f32 sZeroZoomDist;
/**
* The camera's pitch in C-Up mode. Mainly controls mario's head rotation.
*/
s16 sCUpCameraPitch;
/**
* The current mode's yaw, which gets added to the camera's yaw.
*/
s16 sModeOffsetYaw;
/**
* Stores mario's yaw around the stairs, relative to the camera's position.
*
* Used in update_spiral_stairs_camera()
*/
s16 sSpiralStairsYawOffset;
/**
* The constant offset to 8-direction mode's yaw.
*/
s16 s8DirModeBaseYaw;
/**
* Player-controlled yaw offset in 8-direction mode, a multiple of 45 degrees.
*/
s16 s8DirModeYawOffset;
/**
* The distance that the camera will look ahead of mario in the direction mario is facing.
*/
f32 sPanDistance;
/**
* When mario gets in the cannon, it is pointing straight up and rotates down.
* This is used to make the camera start up and rotate down, like the cannon.
*/
f32 sCannonYOffset;
/**
* These structs are used by the cutscenes. Most of the fields are unused, and some (all?) of the used
* ones have multiple uses.
* Check the cutscene_start functions for documentation on the cvars used by a specific cutscene.
*/
struct CutsceneVariable sCutsceneVars[10];
struct ModeTransitionInfo sModeInfo;
/**
* Offset added to sFixedModeBasePosition when mario is inside, near the castle lobby entrance
*/
Vec3f sCastleEntranceOffset;
/**
* The index into the current parallel tracking path
*/
u32 sParTrackIndex;
/**
* The current list of ParallelTrackingPoints used in update_parallel_tracking_camera()
*/
struct ParallelTrackingPoint *sParTrackPath;
/**
* On the first frame after the camera changes to a different parallel tracking path, this stores the
* displacement between the camera's calculated new position and its previous positions
*
* This transition offset is then used to smoothly interpolate the camera's position between the two
* paths
*/
struct CameraStoredInfo sParTrackTransOff;
/**
* The information stored when C-Up is active, used to update lakitu's rotation when exiting C-Up
*/
struct CameraStoredInfo sCameraStoreCUp;
/**
* The information stored during cutscenes
*/
struct CameraStoredInfo sCameraStoreCutscene;
// first iteration of data
u32 unused8032CFC0 = 0;
struct Object *gCutsceneFocus = NULL;
// other camera focuses?
u32 unused8032CFC8 = 0;
u32 unused8032CFCC = 0;
struct Object *gSecondCameraFocus = NULL;
/**
* How fast the camera's yaw should approach the next yaw.
*/
s16 sYawSpeed = 0x400;
s32 gCurrLevelArea = 0;
u32 gPrevLevel = 0;
f32 unused8032CFE0 = 1000.0f;
f32 unused8032CFE4 = 800.0f;
u32 unused8032CFE8 = 0;
f32 gCameraZoomDist = 800.0f;
/**
* A cutscene that plays when the player interacts with an object
*/
u8 sObjectCutscene = 0;
/**
* The ID of the cutscene that ended. It's set to 0 if no cutscene ended less than 8 frames ago.
*
* It is only used to prevent the same cutscene from playing twice before 8 frames have passed.
*/
u8 gRecentCutscene = 0;
/**
* A timer that increments for 8 frames when a cutscene ends.
* When it reaches 8, it sets gRecentCutscene to 0.
*/
u8 sFramesSinceCutsceneEnded = 0;
/**
* Mario's response to a dialog.
* 0 = No response yet
* 1 = Yes
* 2 = No
* 3 = Dialog doesn't have a response
*/
u8 sCutsceneDialogResponse = 0;
struct PlayerCameraState *sMarioCamState = &gPlayerCameraState[0];
struct PlayerCameraState *sLuigiCamState = &gPlayerCameraState[1];
u32 unused8032D008 = 0;
Vec3f sFixedModeBasePosition = { 646.0f, 143.0f, -1513.0f };
Vec3f sUnusedModeBasePosition_2 = { 646.0f, 143.0f, -1513.0f };
Vec3f sUnusedModeBasePosition_3 = { 646.0f, 143.0f, -1513.0f };
Vec3f sUnusedModeBasePosition_4 = { 646.0f, 143.0f, -1513.0f };
Vec3f sUnusedModeBasePosition_5 = { 646.0f, 143.0f, -1513.0f };
s32 update_radial_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_outward_radial_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_behind_mario_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_mario_camera(struct Camera *c, Vec3f, Vec3f);
s32 unused_update_mode_5_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_c_up(struct Camera *c, Vec3f, Vec3f);
s32 nop_update_water_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_slide_or_0f_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_in_cannon(struct Camera *c, Vec3f, Vec3f);
s32 update_boss_fight_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_parallel_tracking_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_fixed_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_8_directions_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_slide_or_0f_camera(struct Camera *c, Vec3f, Vec3f);
s32 update_spiral_stairs_camera(struct Camera *c, Vec3f, Vec3f);
typedef s32 (*CameraTransition)(struct Camera *c, Vec3f, Vec3f);
CameraTransition sModeTransitions[] = {
NULL,
update_radial_camera,
update_outward_radial_camera,
update_behind_mario_camera,
update_mario_camera,
unused_update_mode_5_camera,
update_c_up,
update_mario_camera,
nop_update_water_camera,
update_slide_or_0f_camera,
update_in_cannon,
update_boss_fight_camera,
update_parallel_tracking_camera,
update_fixed_camera,
update_8_directions_camera,
update_slide_or_0f_camera,
update_mario_camera,
update_spiral_stairs_camera
};
// Move these two tables to another include file?
extern u8 sDanceCutsceneIndexTable[][4];
extern u8 sZoomOutAreaMasks[];
/**
* Starts a camera shake triggered by an interaction
*/
void set_camera_shake_from_hit(s16 shake) {
switch (shake) {
// Makes the camera stop for a bit
case SHAKE_ATTACK:
gLakituState.focHSpeed = 0;
gLakituState.posHSpeed = 0;
break;
case SHAKE_FALL_DAMAGE:
set_camera_pitch_shake(0x60, 0x3, 0x8000);
set_camera_roll_shake(0x60, 0x3, 0x8000);
break;
case SHAKE_GROUND_POUND:
set_camera_pitch_shake(0x60, 0xC, 0x8000);
break;
case SHAKE_SMALL_DAMAGE:
if (sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER)) {
set_camera_yaw_shake(0x200, 0x10, 0x1000);
set_camera_roll_shake(0x400, 0x20, 0x1000);
set_fov_shake(0x100, 0x30, 0x8000);
} else {
set_camera_yaw_shake(0x80, 0x8, 0x4000);
set_camera_roll_shake(0x80, 0x8, 0x4000);
set_fov_shake(0x100, 0x30, 0x8000);
}
gLakituState.focHSpeed = 0;
gLakituState.posHSpeed = 0;
break;
case SHAKE_MED_DAMAGE:
if (sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER)) {
set_camera_yaw_shake(0x400, 0x20, 0x1000);
set_camera_roll_shake(0x600, 0x30, 0x1000);
set_fov_shake(0x180, 0x40, 0x8000);
} else {
set_camera_yaw_shake(0x100, 0x10, 0x4000);
set_camera_roll_shake(0x100, 0x10, 0x4000);
set_fov_shake(0x180, 0x40, 0x8000);
}
gLakituState.focHSpeed = 0;
gLakituState.posHSpeed = 0;
break;
case SHAKE_LARGE_DAMAGE:
if (sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER)) {
set_camera_yaw_shake(0x600, 0x30, 0x1000);
set_camera_roll_shake(0x800, 0x40, 0x1000);
set_fov_shake(0x200, 0x50, 0x8000);
} else {
set_camera_yaw_shake(0x180, 0x20, 0x4000);
set_camera_roll_shake(0x200, 0x20, 0x4000);
set_fov_shake(0x200, 0x50, 0x8000);
}
gLakituState.focHSpeed = 0;
gLakituState.posHSpeed = 0;
break;
case SHAKE_HIT_FROM_BELOW:
gLakituState.focHSpeed = 0.07;
gLakituState.posHSpeed = 0.07;
break;
case SHAKE_SHOCK:
set_camera_pitch_shake(random_float() * 64.f, 0x8, 0x8000);
set_camera_yaw_shake(random_float() * 64.f, 0x8, 0x8000);
break;
}
}
/**
* Start a shake from the environment
*/
void set_environmental_camera_shake(s16 shake) {
switch (shake) {
case SHAKE_ENV_EXPLOSION:
set_camera_pitch_shake(0x60, 0x8, 0x4000);
break;
case SHAKE_ENV_BOWSER_THROW_BOUNCE:
set_camera_pitch_shake(0xC0, 0x8, 0x4000);
break;
case SHAKE_ENV_BOWSER_JUMP:
set_camera_pitch_shake(0x100, 0x8, 0x3000);
break;
case SHAKE_ENV_UNUSED_6:
set_camera_roll_shake(0x80, 0x10, 0x3000);
break;
case SHAKE_ENV_UNUSED_7:
set_camera_pitch_shake(0x20, 0x8, 0x8000);
break;
case SHAKE_ENV_PYRAMID_EXPLODE:
set_camera_pitch_shake(0x40, 0x8, 0x8000);
break;
case SHAKE_ENV_JRB_SHIP_DRAIN:
set_camera_pitch_shake(0x20, 0x8, 0x8000);
set_camera_roll_shake(0x400, 0x10, 0x100);
break;
case SHAKE_ENV_FALLING_BITS_PLAT:
set_camera_pitch_shake(0x40, 0x2, 0x8000);
break;
case SHAKE_ENV_UNUSED_5:
set_camera_yaw_shake(-0x200, 0x80, 0x200);
break;
}
}
/**
* Starts a camera shake, but scales the amplitude by the point's distance from the camera
*/
void set_camera_shake_from_point(s16 shake, f32 posX, f32 posY, f32 posZ) {
switch (shake) {
case SHAKE_POS_BOWLING_BALL:
set_pitch_shake_from_point(0x28, 0x8, 0x4000, 2000.f, posX, posY, posZ);
break;
case SHAKE_POS_SMALL:
set_pitch_shake_from_point(0x80, 0x8, 0x4000, 4000.f, posX, posY, posZ);
set_fov_shake_from_point_preset(SHAKE_FOV_SMALL, posX, posY, posZ);
break;
case SHAKE_POS_MEDIUM:
set_pitch_shake_from_point(0xC0, 0x8, 0x4000, 6000.f, posX, posY, posZ);
set_fov_shake_from_point_preset(SHAKE_FOV_MEDIUM, posX, posY, posZ);
break;
case SHAKE_POS_LARGE:
set_pitch_shake_from_point(0x100, 0x8, 0x3000, 8000.f, posX, posY, posZ);
set_fov_shake_from_point_preset(SHAKE_FOV_LARGE, posX, posY, posZ);
break;
}
}
/**
* Start a camera shake from an environmental source, but only shake the camera's pitch.
*/
void unused_set_camera_pitch_shake_env(s16 shake) {
switch (shake) {
case SHAKE_ENV_EXPLOSION:
set_camera_pitch_shake(0x60, 0x8, 0x4000);
break;
case SHAKE_ENV_BOWSER_THROW_BOUNCE:
set_camera_pitch_shake(0xC0, 0x8, 0x4000);
break;
case SHAKE_ENV_BOWSER_JUMP:
set_camera_pitch_shake(0x100, 0x8, 0x3000);
break;
}
}
/**
* Calculates mario's distance to the floor, or the water level if it is above the floor. Then:
* `posOff` is set to the distance multiplied by posMul and bounded to [-posBound, posBound]
* `focOff` is set to the distance multiplied by focMul and bounded to [-focBound, focBound]
*
* Notes:
* posMul is always 1.0f, focMul is always 0.9f
* both ranges are always 200.f
* Since focMul is 0.9, `focOff` is closer to the floor than `posOff`
* posOff and focOff are sometimes the same address, which just ignores the pos calculation
*! Doesn't return anything, but required to match EU
*/
f32 calc_y_to_curr_floor(f32 *posOff, f32 posMul, f32 posBound, f32 *focOff, f32 focMul, f32 focBound) {
f32 floorHeight = sMarioGeometry.currFloorHeight;
f32 waterHeight;
UNUSED s32 filler;
if (!(sMarioCamState->action & ACT_FLAG_METAL_WATER)) {
//! @bug this should use sMarioGeometry.waterHeight
if (floorHeight < (waterHeight = find_water_level(sMarioCamState->pos[0], sMarioCamState->pos[2]))) {
floorHeight = waterHeight;
}
}
if (sMarioCamState->action & ACT_FLAG_ON_POLE) {
if (sMarioGeometry.currFloorHeight >= gMarioStates[0].usedObj->oPosY && sMarioCamState->pos[1]
< 0.7f * gMarioStates[0].usedObj->hitboxHeight + gMarioStates[0].usedObj->oPosY) {
posBound = 1200;
}
}
*posOff = (floorHeight - sMarioCamState->pos[1]) * posMul;
if (*posOff > posBound) {
*posOff = posBound;
}
if (*posOff < -posBound) {
*posOff = -posBound;
}
*focOff = (floorHeight - sMarioCamState->pos[1]) * focMul;
if (*focOff > focBound) {
*focOff = focBound;
}
if (*focOff < -focBound) {
*focOff = -focBound;
}
}
//Compiler gets mad if I put this any further above. thanks refresh 7
#ifdef BETTERCAMERA
#include "bettercamera.inc.h"
#endif
void focus_on_mario(Vec3f focus, Vec3f pos, f32 posYOff, f32 focYOff, f32 dist, s16 pitch, s16 yaw) {
Vec3f marioPos;
marioPos[0] = sMarioCamState->pos[0];
marioPos[1] = sMarioCamState->pos[1] + posYOff;
marioPos[2] = sMarioCamState->pos[2];
vec3f_set_dist_and_angle(marioPos, pos, dist, pitch + sLakituPitch, yaw);
focus[0] = sMarioCamState->pos[0];
focus[1] = sMarioCamState->pos[1] + focYOff;
focus[2] = sMarioCamState->pos[2];
}
static UNUSED void set_pos_to_mario(Vec3f foc, Vec3f pos, f32 yOff, f32 focYOff, f32 dist, s16 pitch, s16 yaw) {
Vec3f marioPos;
f32 posDist;
f32 focDist;
s16 posPitch;
s16 posYaw;
s16 focPitch;
s16 focYaw;
vec3f_copy(marioPos, sMarioCamState->pos);
marioPos[1] += yOff;
vec3f_set_dist_and_angle(marioPos, pos, dist, pitch + sLakituPitch, yaw);
vec3f_get_dist_and_angle(pos, sMarioCamState->pos, &posDist, &posPitch, &posYaw);
//! Useless get and set
vec3f_get_dist_and_angle(pos, foc, &focDist, &focPitch, &focYaw);
vec3f_set_dist_and_angle(pos, foc, focDist, focPitch, focYaw);
foc[1] = sMarioCamState->pos[1] + focYOff;
}
/**
* Set the camera's y coordinate to goalHeight, respecting floors and ceilings in the way
*/
void set_camera_height(struct Camera *c, f32 goalHeight) {
struct Surface *surface;
f32 marioFloorHeight;
f32 marioCeilHeight;
f32 camFloorHeight;
UNUSED u8 filler[8];
UNUSED s16 action = sMarioCamState->action;
f32 baseOff = 125.f;
f32 camCeilHeight = find_ceil(c->pos[0], gLakituState.goalPos[1] - 50.f, c->pos[2], &surface);
if (sMarioCamState->action & ACT_FLAG_HANGING) {
marioCeilHeight = sMarioGeometry.currCeilHeight;
marioFloorHeight = sMarioGeometry.currFloorHeight;
if (marioFloorHeight < marioCeilHeight - 400.f) {
marioFloorHeight = marioCeilHeight - 400.f;
}
goalHeight = marioFloorHeight + (marioCeilHeight - marioFloorHeight) * 0.4f;
if (sMarioCamState->pos[1] - 400 > goalHeight) {
goalHeight = sMarioCamState->pos[1] - 400;
}
approach_camera_height(c, goalHeight, 5.f);
} else {
camFloorHeight = find_floor(c->pos[0], c->pos[1] + 100.f, c->pos[2], &surface) + baseOff;
marioFloorHeight = baseOff + sMarioGeometry.currFloorHeight;
if (camFloorHeight < marioFloorHeight) {
camFloorHeight = marioFloorHeight;
}
if (goalHeight < camFloorHeight) {
goalHeight = camFloorHeight;
c->pos[1] = goalHeight;
}
// Warp camera to goalHeight if further than 1000 and mario is stuck in the ground
if (sMarioCamState->action == ACT_BUTT_STUCK_IN_GROUND ||
sMarioCamState->action == ACT_HEAD_STUCK_IN_GROUND ||
sMarioCamState->action == ACT_FEET_STUCK_IN_GROUND) {
if (ABS(c->pos[1] - goalHeight) > 1000.f) {
c->pos[1] = goalHeight;
}
}
approach_camera_height(c, goalHeight, 20.f);
if (camCeilHeight != 20000.f) {
camCeilHeight -= baseOff;
if ((c->pos[1] > camCeilHeight && sMarioGeometry.currFloorHeight + baseOff < camCeilHeight)
|| (sMarioGeometry.currCeilHeight != 20000.f
&& sMarioGeometry.currCeilHeight > camCeilHeight && c->pos[1] > camCeilHeight)) {
c->pos[1] = camCeilHeight;
}
}
}
}
/**
* Pitch the camera down when the camera is facing down a slope
*/
s16 look_down_slopes(s16 camYaw) {
struct Surface *floor;
f32 floorDY;
// Default pitch
s16 pitch = 0x05B0;
// x and z offsets towards the camera
f32 xOff = sMarioCamState->pos[0] + sins(camYaw) * 40.f;
f32 zOff = sMarioCamState->pos[2] + coss(camYaw) * 40.f;
floorDY = find_floor(xOff, sMarioCamState->pos[1], zOff, &floor) - sMarioCamState->pos[1];
if (floor != NULL) {
if (floor->type != SURFACE_WALL_MISC && floorDY > 0) {
if (floor->normal.z == 0.f && floorDY < 100.f) {
pitch = 0x05B0;
} else {
// Add the slope's angle of declination to the pitch
pitch += atan2s(40.f, floorDY);
}
}
}
return pitch;
}
/**
* Look ahead to the left or right in the direction the player is facing
* The calculation for pan[0] could be simplified to:
* yaw = -yaw;
* pan[0] = sins(sMarioCamState->faceAngle[1] + yaw) * sins(0xC00) * dist;
* Perhaps, early in development, the pan used to be calculated for both the x and z directions
*
* Since this function only affects the camera's focus, mario's movement direction isn't affected.
*/
void pan_ahead_of_player(struct Camera *c) {
f32 dist;
s16 pitch;
s16 yaw;
Vec3f pan = { 0, 0, 0 };
// Get distance and angle from camera to mario.
vec3f_get_dist_and_angle(c->pos, sMarioCamState->pos, &dist, &pitch, &yaw);
// The camera will pan ahead up to about 30% of the camera's distance to mario.
pan[2] = sins(0xC00) * dist;
rotate_in_xz(pan, pan, sMarioCamState->faceAngle[1]);
// rotate in the opposite direction
yaw = -yaw;
rotate_in_xz(pan, pan, yaw);
// Only pan left or right
pan[2] = 0.f;
// If mario is long jumping, or on a flag pole (but not at the top), then pan in the opposite direction
if (sMarioCamState->action == ACT_LONG_JUMP ||
(sMarioCamState->action != ACT_TOP_OF_POLE && (sMarioCamState->action & ACT_FLAG_ON_POLE))) {
pan[0] = -pan[0];
}
// Slowly make the actual pan, sPanDistance, approach the calculated pan
// If mario is sleeping, then don't pan
if (sStatusFlags & CAM_FLAG_SLEEPING) {
approach_f32_asymptotic_bool(&sPanDistance, 0.f, 0.025f);
} else {
approach_f32_asymptotic_bool(&sPanDistance, pan[0], 0.025f);
}
// Now apply the pan. It's a dir vector to the left or right, rotated by the camera's yaw to mario
pan[0] = sPanDistance;
yaw = -yaw;
rotate_in_xz(pan, pan, yaw);
vec3f_add(c->focus, pan);
}
s16 find_in_bounds_yaw_wdw_bob_thi(Vec3f pos, Vec3f origin, s16 yaw) {
switch (gCurrLevelArea) {
case AREA_WDW_MAIN:
yaw = clamp_positions_and_find_yaw(pos, origin, 4508.f, -3739.f, 4508.f, -3739.f);
break;
case AREA_BOB:
yaw = clamp_positions_and_find_yaw(pos, origin, 8000.f, -8000.f, 7050.f, -8000.f);
break;
case AREA_THI_HUGE:
yaw = clamp_positions_and_find_yaw(pos, origin, 8192.f, -8192.f, 8192.f, -8192.f);
break;
case AREA_THI_TINY:
yaw = clamp_positions_and_find_yaw(pos, origin, 2458.f, -2458.f, 2458.f, -2458.f);
break;
}
return yaw;
}
/**
* Rotates the camera around the area's center point.
*/
s32 update_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
f32 cenDistX = sMarioCamState->pos[0] - c->areaCenX;
f32 cenDistZ = sMarioCamState->pos[2] - c->areaCenZ;
s16 camYaw = atan2s(cenDistZ, cenDistX) + sModeOffsetYaw;
s16 pitch = look_down_slopes(camYaw);
UNUSED f32 unused1;
f32 posY;
f32 focusY;
UNUSED f32 unused2;
UNUSED f32 unused3;
f32 yOff = 125.f;
f32 baseDist = 1000.f;
sAreaYaw = camYaw - sModeOffsetYaw;
calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f);
focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw);
camYaw = find_in_bounds_yaw_wdw_bob_thi(pos, focus, camYaw);
return camYaw;
}
/**
* Update the camera during 8 directional mode
*/
s32 update_8_directions_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
UNUSED f32 cenDistX = sMarioCamState->pos[0] - c->areaCenX;
UNUSED f32 cenDistZ = sMarioCamState->pos[2] - c->areaCenZ;
s16 camYaw = s8DirModeBaseYaw + s8DirModeYawOffset;
s16 pitch = look_down_slopes(camYaw);
f32 posY;
f32 focusY;
UNUSED f32 unused1;
UNUSED f32 unused2;
UNUSED f32 unused3;
f32 yOff = 125.f;
f32 baseDist = 1000.f;
sAreaYaw = camYaw;
calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f);
focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw);
pan_ahead_of_player(c);
if (gCurrLevelArea == AREA_DDD_SUB) {
camYaw = clamp_positions_and_find_yaw(pos, focus, 6839.f, 995.f, 5994.f, -3945.f);
}
return camYaw;
}
/**
* Moves the camera for the radial and outward radial camera modes.
*
* If sModeOffsetYaw is 0, the camera points directly at the area center point.
*/
void radial_camera_move(struct Camera *c) {
s16 maxAreaYaw = DEGREES(60);
s16 minAreaYaw = DEGREES(-60);
s16 rotateSpeed = 0x1000;
s16 avoidYaw;
s32 avoidStatus;
UNUSED s16 unused1 = 0;
UNUSED s32 unused2 = 0;
f32 areaDistX = sMarioCamState->pos[0] - c->areaCenX;
f32 areaDistZ = sMarioCamState->pos[2] - c->areaCenZ;
UNUSED s32 filler;
// How much the camera's yaw changed
s16 yawOffset = calculate_yaw(sMarioCamState->pos, c->pos) - atan2s(areaDistZ, areaDistX);
if (yawOffset > maxAreaYaw) {
yawOffset = maxAreaYaw;
}
if (yawOffset < minAreaYaw) {
yawOffset = minAreaYaw;
}
// Check if mario stepped on a surface that rotates the camera. For example, when mario enters the
// gate in BoB, the camera turns right to face up the hill path
if (!(gCameraMovementFlags & CAM_MOVE_ROTATE)) {
if (sMarioGeometry.currFloorType == SURFACE_CAMERA_MIDDLE
&& sMarioGeometry.prevFloorType != SURFACE_CAMERA_MIDDLE) {
gCameraMovementFlags |= (CAM_MOVE_RETURN_TO_MIDDLE | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
if (sMarioGeometry.currFloorType == SURFACE_CAMERA_ROTATE_RIGHT
&& sMarioGeometry.prevFloorType != SURFACE_CAMERA_ROTATE_RIGHT) {
gCameraMovementFlags |= (CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
if (sMarioGeometry.currFloorType == SURFACE_CAMERA_ROTATE_LEFT
&& sMarioGeometry.prevFloorType != SURFACE_CAMERA_ROTATE_LEFT) {
gCameraMovementFlags |= (CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
}
if (gCameraMovementFlags & CAM_MOVE_ENTERED_ROTATE_SURFACE) {
rotateSpeed = 0x200;
}
if (c->mode == CAMERA_MODE_OUTWARD_RADIAL) {
areaDistX = -areaDistX;
areaDistZ = -areaDistZ;
}
// Avoid obstructing walls
avoidStatus = rotate_camera_around_walls(c, c->pos, &avoidYaw, 0x400);
if (avoidStatus == 3) {
if (avoidYaw - atan2s(areaDistZ, areaDistX) + DEGREES(90) < 0) {
avoidYaw += DEGREES(180);
}
// We want to change sModeOffsetYaw so that the player is no longer obstructed by the wall.
// So, we make avoidYaw relative to the yaw around the area center
avoidYaw -= atan2s(areaDistZ, areaDistX);
// Bound avoid yaw to radial mode constraints
if (avoidYaw > DEGREES(105)) {
avoidYaw = DEGREES(105);
}
if (avoidYaw < DEGREES(-105)) {
avoidYaw = DEGREES(-105);
}
}
if (gCameraMovementFlags & CAM_MOVE_RETURN_TO_MIDDLE) {
if (camera_approach_s16_symmetric_bool(&sModeOffsetYaw, 0, rotateSpeed) == 0) {
gCameraMovementFlags &= ~CAM_MOVE_RETURN_TO_MIDDLE;
}
} else {
// Prevent the player from rotating into obstructing walls
if ((gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT) && avoidStatus == 3
&& avoidYaw + 0x10 < sModeOffsetYaw) {
sModeOffsetYaw = avoidYaw;
gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
if ((gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT) && avoidStatus == 3
&& avoidYaw - 0x10 > sModeOffsetYaw) {
sModeOffsetYaw = avoidYaw;
gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
// If it's the first time rotating, just rotate to +-60 degrees
if (!(s2ndRotateFlags & CAM_MOVE_ROTATE_RIGHT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT)
&& camera_approach_s16_symmetric_bool(&sModeOffsetYaw, maxAreaYaw, rotateSpeed) == 0) {
gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
if (!(s2ndRotateFlags & CAM_MOVE_ROTATE_LEFT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT)
&& camera_approach_s16_symmetric_bool(&sModeOffsetYaw, minAreaYaw, rotateSpeed) == 0) {
gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
}
// If it's the second time rotating, rotate all the way to +-105 degrees.
if ((s2ndRotateFlags & CAM_MOVE_ROTATE_RIGHT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_RIGHT)
&& camera_approach_s16_symmetric_bool(&sModeOffsetYaw, DEGREES(105), rotateSpeed) == 0) {
gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_RIGHT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
s2ndRotateFlags &= ~CAM_MOVE_ROTATE_RIGHT;
}
if ((s2ndRotateFlags & CAM_MOVE_ROTATE_LEFT) && (gCameraMovementFlags & CAM_MOVE_ROTATE_LEFT)
&& camera_approach_s16_symmetric_bool(&sModeOffsetYaw, DEGREES(-105), rotateSpeed) == 0) {
gCameraMovementFlags &= ~(CAM_MOVE_ROTATE_LEFT | CAM_MOVE_ENTERED_ROTATE_SURFACE);
s2ndRotateFlags &= ~CAM_MOVE_ROTATE_LEFT;
}
}
if (!(gCameraMovementFlags & CAM_MOVE_ROTATE)) {
// If not rotating, rotate away from walls obscuring mario from view
if (avoidStatus == 3) {
approach_s16_asymptotic_bool(&sModeOffsetYaw, avoidYaw, 10);
} else {
if (c->mode == CAMERA_MODE_RADIAL) {
// sModeOffsetYaw only updates when mario is moving
rotateSpeed = gMarioStates[0].forwardVel / 32.f * 128.f;
camera_approach_s16_symmetric_bool(&sModeOffsetYaw, yawOffset, rotateSpeed);
}
if (c->mode == CAMERA_MODE_OUTWARD_RADIAL) {
sModeOffsetYaw = offset_yaw_outward_radial(c, atan2s(areaDistZ, areaDistX));
}
}
}
// Bound sModeOffsetYaw within (-120, 120) degrees
if (sModeOffsetYaw > 0x5554) {
sModeOffsetYaw = 0x5554;
}
if (sModeOffsetYaw < -0x5554) {
sModeOffsetYaw = -0x5554;
}
}
/**
* Moves lakitu from zoomed in to zoomed out and vice versa.
* When C-Down mode is not active, sLakituDist and sLakituPitch decrease to 0.
*/
void lakitu_zoom(f32 rangeDist, s16 rangePitch) {
if (sLakituDist < 0) {
if ((sLakituDist += 30) > 0) {
sLakituDist = 0;
}
} else if (rangeDist < sLakituDist) {
if ((sLakituDist -= 30) < rangeDist) {
sLakituDist = rangeDist;
}
} else if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) {
if ((sLakituDist += 30) > rangeDist) {
sLakituDist = rangeDist;
}
} else {
if ((sLakituDist -= 30) < 0) {
sLakituDist = 0;
}
}
if (gCurrLevelArea == AREA_SSL_PYRAMID && gCamera->mode == CAMERA_MODE_OUTWARD_RADIAL) {
rangePitch /= 2;
}
if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) {
if ((sLakituPitch += rangePitch / 13) > rangePitch) {
sLakituPitch = rangePitch;
}
} else {
if ((sLakituPitch -= rangePitch / 13) < 0) {
sLakituPitch = 0;
}
}
}
void radial_camera_input_default(struct Camera *c) {
radial_camera_input(c, 0.f);
}
/**
* Makes lakitu cam's yaw match the angle turned towards in C-Up mode, and makes lakitu slowly fly back
* to the distance he was at before C-Up
*/
void update_yaw_and_dist_from_c_up(UNUSED struct Camera *c) {
f32 dist = 1000.f;
sModeOffsetYaw = sModeInfo.transitionStart.yaw - sAreaYaw;
sLakituDist = sModeInfo.transitionStart.dist - dist;
// No longer in C-Up
gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE;
}
/**
* Handles input and updates for the radial camera mode
*/
void mode_radial_camera(struct Camera *c) {
Vec3f pos;
UNUSED u8 unused1[8];
s16 oldAreaYaw = sAreaYaw;
UNUSED u8 unused2[4];
if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) {
update_yaw_and_dist_from_c_up(c);
}
radial_camera_input_default(c);
radial_camera_move(c);
if (c->mode == CAMERA_MODE_RADIAL) {
lakitu_zoom(400.f, 0x900);
}
c->nextYaw = update_radial_camera(c, c->focus, pos);
c->pos[0] = pos[0];
c->pos[2] = pos[2];
sAreaYawChange = sAreaYaw - oldAreaYaw;
if (sMarioCamState->action == ACT_RIDING_HOOT) {
pos[1] += 500.f;
}
set_camera_height(c, pos[1]);
pan_ahead_of_player(c);
}
/**
* A mode that only has 8 camera angles, 45 degrees apart
*/
void mode_8_directions_camera(struct Camera *c) {
Vec3f pos;
UNUSED u8 unused[8];
s16 oldAreaYaw = sAreaYaw;
radial_camera_input(c, 0.f);
if (gPlayer1Controller->buttonPressed & R_CBUTTONS) {
s8DirModeYawOffset += DEGREES(45);
play_sound_cbutton_side();
}
if (gPlayer1Controller->buttonPressed & L_CBUTTONS) {
s8DirModeYawOffset -= DEGREES(45);
play_sound_cbutton_side();
}
lakitu_zoom(400.f, 0x900);
c->nextYaw = update_8_directions_camera(c, c->focus, pos);
c->pos[0] = pos[0];
c->pos[2] = pos[2];
sAreaYawChange = sAreaYaw - oldAreaYaw;
set_camera_height(c, pos[1]);
}
/**
* Updates the camera in outward radial mode.
* sModeOffsetYaw is calculated in radial_camera_move, which calls offset_yaw_outward_radial
*/
s32 update_outward_radial_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
f32 xDistFocToMario = sMarioCamState->pos[0] - c->areaCenX;
f32 zDistFocToMario = sMarioCamState->pos[2] - c->areaCenZ;
s16 camYaw = atan2s(zDistFocToMario, xDistFocToMario) + sModeOffsetYaw + DEGREES(180);
s16 pitch = look_down_slopes(camYaw);
f32 baseDist = 1000.f;
// A base offset of 125.f is ~= mario's eye height
f32 yOff = 125.f;
f32 posY;
f32 focusY;
sAreaYaw = camYaw - sModeOffsetYaw - DEGREES(180);
calc_y_to_curr_floor(&posY, 1.f, 200.f, &focusY, 0.9f, 200.f);
focus_on_mario(focus, pos, posY + yOff, focusY + yOff, sLakituDist + baseDist, pitch, camYaw);
return camYaw;
}
/**
* Input and updates for the outward radial mode.
*/
void mode_outward_radial_camera(struct Camera *c) {
Vec3f pos;
s16 oldAreaYaw = sAreaYaw;
if (gCameraMovementFlags & CAM_MOVING_INTO_MODE) {
update_yaw_and_dist_from_c_up(c);
}
radial_camera_input_default(c);
radial_camera_move(c);
lakitu_zoom(400.f, 0x900);
c->nextYaw = update_outward_radial_camera(c, c->focus, pos);
c->pos[0] = pos[0];
c->pos[2] = pos[2];
sAreaYawChange = sAreaYaw - oldAreaYaw;
if (sMarioCamState->action == ACT_RIDING_HOOT) {
pos[1] += 500.f;
}
set_camera_height(c, pos[1]);
pan_ahead_of_player(c);
}
/**
* Move the camera in parallel tracking mode
*
* Uses the line between the next two points in sParTrackPath
* The camera can move forward/back and side to side, but it will face perpendicular to that line
*
* Although, annoyingly, it's not truly parallel, the function returns the yaw from the camera to mario,
* so mario will run slightly towards the camera.
*/
s32 update_parallel_tracking_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
Vec3f path[2];
Vec3f parMidPoint;
Vec3f marioOffset;
Vec3f camOffset;
/// Adjusts the focus to look where mario is facing. Unused since marioOffset is copied to focus
Vec3f focOffset;
s16 pathPitch;
s16 pathYaw;
UNUSED u8 filler[4];
f32 distThresh;
f32 zoom;
f32 camParDist;
UNUSED u8 filler2[8];
f32 pathLength;
UNUSED u8 filler3[8];
UNUSED f32 unusedScale = 0.5f;
f32 parScale = 0.5f;
f32 marioFloorDist;
Vec3f marioPos;
UNUSED u8 filler4[12];
UNUSED Vec3f unused4;
Vec3s pathAngle;
// Variables for changing to the next/prev path in the list
Vec3f oldPos;
Vec3f prevPathPos;
Vec3f nextPathPos;
f32 distToNext;
f32 distToPrev;
s16 prevPitch;
s16 nextPitch;
s16 prevYaw;
s16 nextYaw;
unused4[0] = 0.f;
unused4[1] = 0.f;
unused4[2] = 0.f;
// Store camera pos, for changing between paths
vec3f_copy(oldPos, pos);
vec3f_copy(path[0], sParTrackPath[sParTrackIndex].pos);
vec3f_copy(path[1], sParTrackPath[sParTrackIndex + 1].pos);
distThresh = sParTrackPath[sParTrackIndex].distThresh;
zoom = sParTrackPath[sParTrackIndex].zoom;
calc_y_to_curr_floor(&marioFloorDist, 1.f, 200.f, &marioFloorDist, 0.9f, 200.f);
marioPos[0] = sMarioCamState->pos[0];
// Mario's y pos + ~mario's height + mario's height above the floor
marioPos[1] = sMarioCamState->pos[1] + 150.f + marioFloorDist;
marioPos[2] = sMarioCamState->pos[2];
// Calculate middle of the path (parScale is 0.5f)
parMidPoint[0] = path[0][0] + (path[1][0] - path[0][0]) * parScale;
parMidPoint[1] = path[0][1] + (path[1][1] - path[0][1]) * parScale;
parMidPoint[2] = path[0][2] + (path[1][2] - path[0][2]) * parScale;
// Get direction of path
vec3f_get_dist_and_angle(path[0], path[1], &pathLength, &pathPitch, &pathYaw);
marioOffset[0] = marioPos[0] - parMidPoint[0];
marioOffset[1] = marioPos[1] - parMidPoint[1];
marioOffset[2] = marioPos[2] - parMidPoint[2];
// Make marioOffset point from the midpoint -> the start of the path
// Rotating by -yaw then -pitch moves the hor dist from the midpoint into marioOffset's z coordinate
// marioOffset[0] = the (perpendicular) horizontal distance from the path
// marioOffset[1] = the vertical distance from the path
// marioOffset[2] = the (parallel) horizontal distance from the path's midpoint
pathYaw = -pathYaw;
rotate_in_xz(marioOffset, marioOffset, pathYaw);
pathYaw = -pathYaw;
pathPitch = -pathPitch;
rotate_in_yz(marioOffset, marioOffset, pathPitch);
pathPitch = -pathPitch;
vec3f_copy(focOffset, marioOffset);
// OK
focOffset[0] = -focOffset[0] * 0.f;
focOffset[1] = focOffset[1] * 0.f;
// Repeat above calcs with camOffset
camOffset[0] = pos[0] - parMidPoint[0];
camOffset[1] = pos[1] - parMidPoint[1];
camOffset[2] = pos[2] - parMidPoint[2];
pathYaw = -pathYaw;
rotate_in_xz(camOffset, camOffset, pathYaw);
pathYaw = -pathYaw;
pathPitch = -pathPitch;
rotate_in_yz(camOffset, camOffset, pathPitch);
pathPitch = -pathPitch;
// If mario is distThresh units away from the camera along the path, move the camera
//! When distThresh != 0, it causes mario to move slightly towards the camera when running sideways
//! Set each ParallelTrackingPoint's distThresh to 0 to make Mario truly run parallel to the path
if (marioOffset[2] > camOffset[2]) {
if (marioOffset[2] - camOffset[2] > distThresh) {
camOffset[2] = marioOffset[2] - distThresh;
}
} else {
if (marioOffset[2] - camOffset[2] < -distThresh) {
camOffset[2] = marioOffset[2] + distThresh;
}
}
// If zoom != 0.0, the camera will move zoom% closer to mario
marioOffset[0] = -marioOffset[0] * zoom;
marioOffset[1] = marioOffset[1] * zoom;
marioOffset[2] = camOffset[2];
//! Does nothing because focOffset[0] is always 0
focOffset[0] *= 0.3f;
//! Does nothing because focOffset[1] is always 0
focOffset[1] *= 0.3f;
pathAngle[0] = pathPitch;
pathAngle[1] = pathYaw; //! No effect
// make marioOffset[2] == distance from the start of the path
marioOffset[2] = pathLength / 2 - marioOffset[2];
pathAngle[1] = pathYaw + DEGREES(180);
pathAngle[2] = 0;
// Rotate the offset in the direction of the path again
offset_rotated(pos, path[0], marioOffset, pathAngle);
vec3f_get_dist_and_angle(path[0], c->pos, &camParDist, &pathPitch, &pathYaw);
// Adjust the focus. Does nothing, focus is set to mario at the end
focOffset[2] = pathLength / 2 - focOffset[2];
offset_rotated(c->focus, path[0], focOffset, pathAngle);
// Changing paths, update the stored position offset
if (sStatusFlags & CAM_FLAG_CHANGED_PARTRACK_INDEX) {
sStatusFlags &= ~CAM_FLAG_CHANGED_PARTRACK_INDEX;
sParTrackTransOff.pos[0] = oldPos[0] - c->pos[0];
sParTrackTransOff.pos[1] = oldPos[1] - c->pos[1];
sParTrackTransOff.pos[2] = oldPos[2] - c->pos[2];
}
// Slowly transition to the next path
approach_f32_asymptotic_bool(&sParTrackTransOff.pos[0], 0.f, 0.025f);
approach_f32_asymptotic_bool(&sParTrackTransOff.pos[1], 0.f, 0.025f);
approach_f32_asymptotic_bool(&sParTrackTransOff.pos[2], 0.f, 0.025f);
vec3f_add(c->pos, sParTrackTransOff.pos);
// Check if the camera should go to the next path
if (sParTrackPath[sParTrackIndex + 1].startOfPath != 0) {
// get Mario's distance to the next path
calculate_angles(sParTrackPath[sParTrackIndex + 1].pos, sParTrackPath[sParTrackIndex + 2].pos, &nextPitch, &nextYaw);
vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex + 1].pos, nextPathPos, 400.f, nextPitch, nextYaw);
distToPrev = calc_abs_dist(marioPos, nextPathPos);
// get Mario's distance to the previous path
calculate_angles(sParTrackPath[sParTrackIndex + 1].pos, sParTrackPath[sParTrackIndex].pos, &prevPitch, &prevYaw);
vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex + 1].pos, prevPathPos, 400.f, prevPitch, prevYaw);
distToNext = calc_abs_dist(marioPos, prevPathPos);
if (distToPrev < distToNext) {
sParTrackIndex++;
sStatusFlags |= CAM_FLAG_CHANGED_PARTRACK_INDEX;
}
}
// Check if the camera should go to the previous path
if (sParTrackIndex != 0) {
// get Mario's distance to the next path
calculate_angles((*(sParTrackPath + sParTrackIndex)).pos, (*(sParTrackPath + sParTrackIndex + 1)).pos, &nextPitch, &nextYaw);
vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex].pos, nextPathPos, 700.f, nextPitch, nextYaw);
distToPrev = calc_abs_dist(marioPos, nextPathPos);
// get Mario's distance to the previous path
calculate_angles((*(sParTrackPath + sParTrackIndex)).pos, (*(sParTrackPath + sParTrackIndex - 1)).pos, &prevPitch, &prevYaw);
vec3f_set_dist_and_angle(sParTrackPath[sParTrackIndex].pos, prevPathPos, 700.f, prevPitch, prevYaw);
distToNext = calc_abs_dist(marioPos, prevPathPos);
if (distToPrev > distToNext) {
sParTrackIndex--;
sStatusFlags |= CAM_FLAG_CHANGED_PARTRACK_INDEX;
}
}
// Update the camera focus and return the camera's yaw
vec3f_copy(focus, marioPos);
vec3f_get_dist_and_angle(focus, pos, &camParDist, &pathPitch, &pathYaw);
return pathYaw;
}
/**
* Updates the camera during fixed mode.
*/
s32 update_fixed_camera(struct Camera *c, Vec3f focus, UNUSED Vec3f pos) {
f32 focusFloorOff;
f32 goalHeight;
f32 ceilHeight;
f32 heightOffset;
f32 distCamToFocus;
UNUSED u8 filler2[8];
f32 scaleToMario = 0.5f;
s16 pitch;
s16 yaw;
Vec3s faceAngle;
struct Surface *ceiling;
Vec3f basePos;
UNUSED u8 filler[12];
play_camera_buzz_if_c_sideways();
// Don't move closer to mario in these areas
switch (gCurrLevelArea) {
case AREA_RR:
scaleToMario = 0.f;
heightOffset = 0.f;
break;
case AREA_CASTLE_LOBBY:
scaleToMario = 0.3f;
heightOffset = 0.f;
break;
case AREA_BBH:
scaleToMario = 0.f;
heightOffset = 0.f;
break;
}
handle_c_button_movement(c);
play_camera_buzz_if_cdown();
calc_y_to_curr_floor(&focusFloorOff, 1.f, 200.f, &focusFloorOff, 0.9f, 200.f);
vec3f_copy(focus, sMarioCamState->pos);
focus[1] += focusFloorOff + 125.f;
vec3f_get_dist_and_angle(focus, c->pos, &distCamToFocus, &faceAngle[0], &faceAngle[1]);
faceAngle[2] = 0;
vec3f_copy(basePos, sFixedModeBasePosition);
vec3f_add(basePos, sCastleEntranceOffset);
if (sMarioGeometry.currFloorType != SURFACE_DEATH_PLANE
&& sMarioGeometry.currFloorHeight != -11000.f) {
goalHeight = sMarioGeometry.currFloorHeight + basePos[1] + heightOffset;
} else {
goalHeight = gLakituState.goalPos[1];
}
if (300 > distCamToFocus) {
goalHeight += 300 - distCamToFocus;
}
ceilHeight = find_ceil(c->pos[0], goalHeight - 100.f, c->pos[2], &ceiling);
if (ceilHeight != 20000.f) {
if (goalHeight > (ceilHeight -= 125.f)) {
goalHeight = ceilHeight;
}
}
if (sStatusFlags & CAM_FLAG_SMOOTH_MOVEMENT) {
camera_approach_f32_symmetric_bool(&c->pos[1], goalHeight, 15.f);
} else {
if (goalHeight < sMarioCamState->pos[1] - 500.f) {
goalHeight = sMarioCamState->pos[1] - 500.f;
}
c->pos[1] = goalHeight;
}
c->pos[0] = basePos[0] + (sMarioCamState->pos[0] - basePos[0]) * scaleToMario;
c->pos[2] = basePos[2] + (sMarioCamState->pos[2] - basePos[2]) * scaleToMario;
if (scaleToMario != 0.f) {
vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &pitch, &yaw);
if (distCamToFocus > 1000.f) {
distCamToFocus = 1000.f;
vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, pitch, yaw);
}
}
return faceAngle[1];
}
/**
* Updates the camera during a boss fight
*/
s32 update_boss_fight_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
struct Object *o;
UNUSED u8 filler2[12];
f32 focusDistance;
UNUSED u8 filler3[4];
// Floor normal values
f32 nx;
f32 ny;
f32 nz;
/// Floor originOffset
f32 oo;
UNUSED u8 filler4[4];
UNUSED s16 unused;
s16 yaw;
s16 heldState;
struct Surface *floor;
UNUSED u8 filler[20];
Vec3f secondFocus;
Vec3f holdFocOffset = { 0.f, -150.f, -125.f };
handle_c_button_movement(c);
// Start camera shakes if bowser jumps or gets thrown.
if (sMarioCamState->cameraEvent == CAM_EVENT_BOWSER_JUMP) {
set_environmental_camera_shake(SHAKE_ENV_BOWSER_JUMP);
sMarioCamState->cameraEvent = 0;
}
if (sMarioCamState->cameraEvent == CAM_EVENT_BOWSER_THROW_BOUNCE) {
set_environmental_camera_shake(SHAKE_ENV_BOWSER_THROW_BOUNCE);
sMarioCamState->cameraEvent = 0;
}
yaw = sModeOffsetYaw + DEGREES(45);
// Get boss's position and whether mario is holding it.
if ((o = gSecondCameraFocus) != NULL) {
object_pos_to_vec3f(secondFocus, o);
heldState = o->oHeldState;
} else {
// If no boss is there, just rotate around the area's center point.
secondFocus[0] = c->areaCenX;
secondFocus[1] = sMarioCamState->pos[1];
secondFocus[2] = c->areaCenZ;
heldState = 0;
}
focusDistance = calc_abs_dist(sMarioCamState->pos, secondFocus) * 1.6f;
if (focusDistance < 800.f) {
focusDistance = 800.f;
}
if (focusDistance > 5000.f) {
focusDistance = 5000.f;
}
// If holding the boss, add a slight offset to secondFocus so that the spinning is more pronounced.
if (heldState == 1) {
offset_rotated(secondFocus, sMarioCamState->pos, holdFocOffset, sMarioCamState->faceAngle);
}
// Set the camera focus to the average of mario and secondFocus
focus[0] = (sMarioCamState->pos[0] + secondFocus[0]) / 2.f;
focus[1] = (sMarioCamState->pos[1] + secondFocus[1]) / 2.f + 125.f;
focus[2] = (sMarioCamState->pos[2] + secondFocus[2]) / 2.f;
// Calculate the camera's position as an offset from the focus
// When C-Down is not active, this
vec3f_set_dist_and_angle(focus, pos, focusDistance, 0x1000, yaw);
// Find the floor of the arena
pos[1] = find_floor(c->areaCenX, 20000.f, c->areaCenZ, &floor);
if (floor != NULL) {
nx = floor->normal.x;
ny = floor->normal.y;
nz = floor->normal.z;
oo = floor->originOffset;
pos[1] = 300.f - (nx * pos[0] + nz * pos[2] + oo) / ny;
switch (gCurrLevelArea) {
case AREA_BOB:
pos[1] += 125.f;
//! fall through, makes the BoB boss fight camera move up twice as high as it should
case AREA_WF:
pos[1] += 125.f;
}
}
//! Must be same line to match EU
// Prevent the camera from going to the ground in the outside boss fight
if (gCurrLevelNum == LEVEL_BBH) { pos[1] = 2047.f; }
// Rotate from C-Button input
if (sCSideButtonYaw < 0) {
sModeOffsetYaw += 0x200;
if ((sCSideButtonYaw += 0x100) > 0) {
sCSideButtonYaw = 0;
}
}
if (sCSideButtonYaw > 0) {
sModeOffsetYaw -= 0x200;
if ((sCSideButtonYaw -= 0x100) < 0) {
sCSideButtonYaw = 0;
}
}
focus[1] = (sMarioCamState->pos[1] + secondFocus[1]) / 2.f + 100.f;
if (heldState == 1) {
focus[1] += 300.f * sins((gMarioStates[0].angleVel[1] > 0.f) ? gMarioStates[0].angleVel[1]
: -gMarioStates[0].angleVel[1]);
}
//! Unnecessary conditional, focusDistance is already bounded to 800
if (focusDistance < 400.f) {
focusDistance = 400.f;
}
// Set C-Down distance and pitch.
// C-Down will essentially double the distance from the center.
// sLakituPitch approaches 33.75 degrees.
lakitu_zoom(focusDistance, 0x1800);
// Move the camera position back as sLakituDist and sLakituPitch increase.
// This doesn't zoom out of bounds because pos is set above each frame.
// The constant 0x1000 doubles the pitch from the center when sLakituPitch is 0
// When lakitu is fully zoomed out, the pitch comes to 0x3800, or 78.75 degrees, up from the focus.
vec3f_set_dist_and_angle(pos, pos, sLakituDist, sLakituPitch + 0x1000, yaw);
return yaw;
}
// 2nd iteration of data
s16 unused8032D0A8[] = { 14, 1, 2, 4 };
s16 unused8032D0B0[] = { 16, 9, 17, 0 };
/**
* Maps cutscene to numbers in [0,4]. Used in determine_dance_cutscene() with sDanceCutsceneIndexTable.
*
* Only the first 5 entries are used. Perhaps the last 5 were bools used to indicate whether the star
* type exits the course or not.
*/
u8 sDanceCutsceneTable[] = {
CUTSCENE_DANCE_FLY_AWAY, CUTSCENE_DANCE_ROTATE, CUTSCENE_DANCE_CLOSEUP, CUTSCENE_KEY_DANCE, CUTSCENE_DANCE_DEFAULT,
FALSE, FALSE, FALSE, FALSE, TRUE,
};
/**
* Perhaps used by different dance cutscenes.
*/
struct UnusedDanceInfo {
Vec3f point;
f32 distTarget;
f32 distMultiplier;
};
struct UnusedDanceInfo unusedDanceInfo1 = {
{-3026.0f, 912.0f, -2148.0f},
600.0f,
0.3f
};
u32 unusedDanceType = 0;
struct UnusedDanceInfo unusedDanceInfo2 = {
{-4676.0f, 917.0f, -3802.0f},
600.0f,
0.3f
};
/**
* Table that dictates camera movement in bookend room.
* Due to only the X being varied in the table, this only moves along the X axis linearly.
* Third entry is seemingly unused.
*/
struct ParallelTrackingPoint sBBHLibraryParTrackPath[] = {
{ 1, { -929.0f, 1619.0f, -1490.0f }, 50.0f, 0.0f },
{ 0, { -2118.0f, 1619.0f, -1490.0f }, 50.0f, 0.0f },
{ 0, { 0.0f, 0.0f, 0.0f }, 0.0f, 0.0f },
};
s32 unused_update_mode_5_camera(UNUSED struct Camera *c, UNUSED Vec3f focus, UNUSED Vec3f pos) {
}
static void stub_camera_1(UNUSED s32 unused) {
}
void mode_boss_fight_camera(struct Camera *c) {
c->nextYaw = update_boss_fight_camera(c, c->focus, c->pos);
}
/**
* Parallel tracking mode, the camera faces perpendicular to a line defined by sParTrackPath
*
* @see update_parallel_tracking_camera
*/
void mode_parallel_tracking_camera(struct Camera *c) {
s16 dummy;
radial_camera_input(c, 0.f);
set_fov_function(CAM_FOV_DEFAULT);
c->nextYaw = update_parallel_tracking_camera(c, c->focus, c->pos);
camera_approach_s16_symmetric_bool(&dummy, 0, 0x0400);
}
/**
* Fixed camera mode, the camera rotates around a point and looks and zooms toward mario.
*/
void mode_fixed_camera(struct Camera *c) {
UNUSED u8 unused[8];
if (gCurrLevelNum == LEVEL_BBH) {
set_fov_function(CAM_FOV_BBH);
} else {
set_fov_function(CAM_FOV_APP_45);
}
c->nextYaw = update_fixed_camera(c, c->focus, c->pos);
c->yaw = c->nextYaw;
pan_ahead_of_player(c);
vec3f_set(sCastleEntranceOffset, 0.f, 0.f, 0.f);
}
/**
* Updates the camera in BEHIND_MARIO mode.
*
* The C-Buttons rotate the camera 90 degrees left/right and 67.5 degrees up/down.
*/
s32 update_behind_mario_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
UNUSED u8 unused2[12];
f32 dist;
UNUSED u8 unused3[4];
s16 absPitch;
s16 pitch;
s16 yaw;
s16 goalPitch = -sMarioCamState->faceAngle[0];
s16 marioYaw = sMarioCamState->faceAngle[1] + DEGREES(180);
s16 goalYawOff = 0;
s16 yawSpeed;
s16 pitchInc = 32;
UNUSED u8 unused[12];
f32 maxDist = 800.f;
f32 focYOff = 125.f;
// Zoom in when mario R_TRIG mode is active
if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) {
maxDist = 350.f;
focYOff = 120.f;
}
if (!(sMarioCamState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER))) {
pitchInc = 128;
}
// Focus on mario
vec3f_copy(focus, sMarioCamState->pos);
c->focus[1] += focYOff;
//! @bug unnecessary
dist = calc_abs_dist(focus, pos);
//! @bug unnecessary
pitch = calculate_pitch(focus, pos);
vec3f_get_dist_and_angle(focus, pos, &dist, &pitch, &yaw);
if (dist > maxDist) {
dist = maxDist;
}
if ((absPitch = pitch) < 0) {
absPitch = -absPitch;
}
// Determine the yaw speed based on absPitch. A higher absPitch (further away from looking straight)
// translates to a slower speed
// Note: Pitch is always within +- 90 degrees or +-0x4000, and 0x4000 / 0x200 = 32
yawSpeed = 32 - absPitch / 0x200;
if (yawSpeed < 1) {
yawSpeed = 1;
}
if (yawSpeed > 32) {
yawSpeed = 32;
}
if (sCSideButtonYaw != 0) {
camera_approach_s16_symmetric_bool(&sCSideButtonYaw, 0, 1);
yawSpeed = 8;
}
if (sBehindMarioSoundTimer != 0) {
goalPitch = 0;
camera_approach_s16_symmetric_bool(&sBehindMarioSoundTimer, 0, 1);
pitchInc = 0x800;
}
if (sBehindMarioSoundTimer == 28) {
if (sCSideButtonYaw < 5 || sCSideButtonYaw > 28) {
play_sound_cbutton_up();
}
}
if (sCSideButtonYaw == 28) {
if (sBehindMarioSoundTimer < 5 || sBehindMarioSoundTimer > 28) {
play_sound_cbutton_up();
}
}
// C-Button input. Note: Camera rotates in the opposite direction of the button (airplane controls)
//! @bug C-Right and C-Up take precedence due to the way input is handled here
// Rotate right
if (sCButtonsPressed & L_CBUTTONS) {
if (gPlayer1Controller->buttonPressed & L_CBUTTONS) {
play_sound_cbutton_side();
}
if (dist < maxDist) {
camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f);
}
goalYawOff = -0x3FF8;
sCSideButtonYaw = 30;
yawSpeed = 2;
}
// Rotate left
if (sCButtonsPressed & R_CBUTTONS) {
if (gPlayer1Controller->buttonPressed & R_CBUTTONS) {
play_sound_cbutton_side();
}
if (dist < maxDist) {
camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f);
}
goalYawOff = 0x3FF8;
sCSideButtonYaw = 30;
yawSpeed = 2;
}
// Rotate up
if (sCButtonsPressed & D_CBUTTONS) {
if (gPlayer1Controller->buttonPressed & (U_CBUTTONS | D_CBUTTONS)) {
play_sound_cbutton_side();
}
if (dist < maxDist) {
camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f);
}
goalPitch = -0x3000;
sBehindMarioSoundTimer = 30;
pitchInc = 0x800;
}
// Rotate down
if (sCButtonsPressed & U_CBUTTONS) {
if (gPlayer1Controller->buttonPressed & (U_CBUTTONS | D_CBUTTONS)) {
play_sound_cbutton_side();
}
if (dist < maxDist) {
camera_approach_f32_symmetric_bool(&dist, maxDist, 5.f);
}
goalPitch = 0x3000;
sBehindMarioSoundTimer = 30;
pitchInc = 0x800;
}
approach_s16_asymptotic_bool(&yaw, marioYaw + goalYawOff, yawSpeed);
camera_approach_s16_symmetric_bool(&pitch, goalPitch, pitchInc);
if (dist < 300.f) {
dist = 300.f;
}
vec3f_set_dist_and_angle(focus, pos, dist, pitch, yaw);
if (gCurrLevelArea == AREA_WDW_MAIN) {
yaw = clamp_positions_and_find_yaw(pos, focus, 4508.f, -3739.f, 4508.f, -3739.f);
}
if (gCurrLevelArea == AREA_THI_HUGE) {
yaw = clamp_positions_and_find_yaw(pos, focus, 8192.f, -8192.f, 8192.f, -8192.f);
}
if (gCurrLevelArea == AREA_THI_TINY) {
yaw = clamp_positions_and_find_yaw(pos, focus, 2458.f, -2458.f, 2458.f, -2458.f);
}
return yaw;
}
/**
* "Behind Mario" mode: used when mario is flying, on the water's surface, or shot from a cannon
*/
s32 mode_behind_mario(struct Camera *c) {
struct MarioState *marioState = &gMarioStates[0];
struct Surface *floor;
Vec3f newPos;
//! @bug oldPos is unused, see resolve_geometry_collisions
Vec3f oldPos;
f32 waterHeight;
f32 floorHeight;
f32 distCamToFocus;
s16 camPitch;
s16 camYaw;
s16 yaw;
vec3f_copy(oldPos, c->pos);
gCameraMovementFlags &= ~CAM_MOVING_INTO_MODE;
vec3f_copy(newPos, c->pos);
yaw = update_behind_mario_camera(c, c->focus, newPos);
c->pos[0] = newPos[0];
c->pos[2] = newPos[2];
// Keep the camera above the water surface if swimming
if (c->mode == CAMERA_MODE_WATER_SURFACE) {
floorHeight = find_floor(c->pos[0], c->pos[1], c->pos[2], &floor);
newPos[1] = marioState->waterLevel + 120;
if (newPos[1] < (floorHeight += 120.f)) {
newPos[1] = floorHeight;
}
}
approach_camera_height(c, newPos[1], 50.f);
waterHeight = find_water_level(c->pos[0], c->pos[2]) + 100.f;
if (c->pos[1] <= waterHeight) {
gCameraMovementFlags |= CAM_MOVE_SUBMERGED;
} else {
gCameraMovementFlags &= ~CAM_MOVE_SUBMERGED;
}
resolve_geometry_collisions(c->pos, oldPos);
// Prevent camera getting too far away
vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw);
if (distCamToFocus > 800.f) {
distCamToFocus = 800.f;
vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw);
}
pan_ahead_of_player(c);
return yaw;
}
/**
* Update the camera in slide and hoot mode.
*
* In slide mode, keep the camera 800 units from mario
*/
s16 update_slide_camera(struct Camera *c) {
struct Surface *floor;
f32 floorHeight;
Vec3f pos;
f32 distCamToFocus;
f32 maxCamDist;
f32 pitchScale;
s16 camPitch;
s16 camYaw;
UNUSED struct MarioState *marioState = &gMarioStates[0];
s16 goalPitch = 0x1555;
s16 goalYaw = sMarioCamState->faceAngle[1] + DEGREES(180);
// Zoom in when inside the CCM shortcut
if (sStatusFlags & CAM_FLAG_CCM_SLIDE_SHORTCUT) {
sLakituDist = approach_f32(sLakituDist, -600.f, 20.f, 20.f);
} else {
sLakituDist = approach_f32(sLakituDist, 0.f, 20.f, 20.f);
}
// No C-Button input in this mode, notify the player with a buzzer
play_camera_buzz_if_cbutton();
// Focus on mario
vec3f_copy(c->focus, sMarioCamState->pos);
c->focus[1] += 50.f;
vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw);
maxCamDist = 800.f;
// In hoot mode, zoom further out and rotate faster
if (sMarioCamState->action == ACT_RIDING_HOOT) {
maxCamDist = 1000.f;
goalPitch = 0x2800;
camera_approach_s16_symmetric_bool(&camYaw, goalYaw, 0x100);
} else {
camera_approach_s16_symmetric_bool(&camYaw, goalYaw, 0x80);
}
camera_approach_s16_symmetric_bool(&camPitch, goalPitch, 0x100);
// Hoot mode
if (sMarioCamState->action != ACT_RIDING_HOOT && sMarioGeometry.currFloorType == SURFACE_DEATH_PLANE) {
vec3f_set_dist_and_angle(c->focus, pos, maxCamDist + sLakituDist, camPitch, camYaw);
c->pos[0] = pos[0];
c->pos[2] = pos[2];
camera_approach_f32_symmetric_bool(&c->pos[1], c->focus[1], 30.f);
vec3f_get_dist_and_angle(c->pos, c->focus, &distCamToFocus, &camPitch, &camYaw);
pitchScale = (distCamToFocus - maxCamDist + sLakituDist) / 10000.f;
if (pitchScale > 1.f) {
pitchScale = 1.f;
}
camPitch += 0x1000 * pitchScale;
vec3f_set_dist_and_angle(c->pos, c->focus, distCamToFocus, camPitch, camYaw);
// Slide mode
} else {
vec3f_set_dist_and_angle(c->focus, c->pos, maxCamDist + sLakituDist, camPitch, camYaw);
sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT;
// Stay above the slide floor
floorHeight = find_floor(c->pos[0], c->pos[1] + 200.f, c->pos[2], &floor) + 125.f;
if (c->pos[1] < floorHeight) {
c->pos[1] = floorHeight;
}
// Stay closer than maxCamDist
vec3f_get_dist_and_angle(c->focus, c->pos, &distCamToFocus, &camPitch, &camYaw);
if (distCamToFocus > maxCamDist + sLakituDist) {
distCamToFocus = maxCamDist + sLakituDist;
vec3f_set_dist_and_angle(c->focus, c->pos, distCamToFocus, camPitch, camYaw);
}
}
camYaw = calculate_yaw(c->focus, c->pos);
return camYaw;
}
void mode_behind_mario_camera(struct Camera *c) {
c->nextYaw = mode_behind_mario(c);
}
s32 nop_update_water_camera(UNUSED struct Camera *c, UNUSED Vec3f focus, UNUSED Vec3f pos) {
}
/**
* Exactly the same as BEHIND_MARIO
*/
void mode_water_surface_camera(struct Camera *c) {
c->nextYaw = mode_behind_mario(c);
}
/**
* Used in sModeTransitions for CLOSE and FREE_ROAM mode
*/
s32 update_mario_camera(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) {
s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180);
focus_on_mario(focus, pos, 125.f, 125.f, gCameraZoomDist, 0x05B0, yaw);
return sMarioCamState->faceAngle[1];
}
/**
* Update the camera in default, close, and free roam mode
*
* The camera moves behind mario, and can rotate all the way around
*/
s16 update_default_camera(struct Camera *c) {
Vec3f tempPos;
Vec3f cPos;
UNUSED u8 unused1[12];
struct Surface *marioFloor;
struct Surface *cFloor;
struct Surface *tempFloor;
struct Surface *ceil;
f32 camFloorHeight;
f32 tempFloorHeight;
f32 marioFloorHeight;
UNUSED u8 unused2[4];
f32 dist;
f32 zoomDist;
f32 waterHeight;
f32 gasHeight;
s16 avoidYaw;
s16 pitch;
s16 yaw;
s16 yawGoal = sMarioCamState->faceAngle[1] + DEGREES(180);
f32 posHeight;
f32 focHeight;
f32 distFromWater;
s16 tempPitch;
s16 tempYaw;
f32 xzDist;
UNUSED u8 unused4[4];
s16 nextYawVel;
s16 yawVel = 0;
f32 scale;
s32 avoidStatus = 0;
s32 closeToMario = 0;
f32 ceilHeight = find_ceil(gLakituState.goalPos[0],
gLakituState.goalPos[1],
gLakituState.goalPos[2], &ceil);
s16 yawDir;
handle_c_button_movement(c);
vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw);
// If C-Down is active, determine what distance the camera should be from mario
if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) {
//! In Mario mode, the camera is zoomed out further than in lakitu mode (1400 vs 1200)
if (set_cam_angle(0) == CAM_ANGLE_MARIO) {
zoomDist = gCameraZoomDist + 1050;
} else {
zoomDist = gCameraZoomDist + 400;
}
} else {
zoomDist = gCameraZoomDist;
}
if (sMarioCamState->action & ACT_FLAG_HANGING ||
sMarioCamState->action == ACT_RIDING_HOOT) {
zoomDist *= 0.8f;
set_handheld_shake(HAND_CAM_SHAKE_HANG_OWL);
}
// If not zooming out, only allow dist to decrease
if (sZoomAmount == 0.f) {
if (dist > zoomDist) {
if ((dist -= 50.f) < zoomDist) {
dist = zoomDist;
}
}
} else {
if ((sZoomAmount -= 30.f) < 0.f) {
sZoomAmount = 0.f;
}
if (dist > zoomDist) {
if ((dist -= 30.f) < zoomDist) {
dist = zoomDist;
}
}
if (dist < zoomDist) {
if ((dist += 30.f) > zoomDist) {
dist = zoomDist;
}
}
}
// Determine how fast to rotate the camera
if (sCSideButtonYaw == 0) {
if (c->mode == CAMERA_MODE_FREE_ROAM) {
nextYawVel = 0xC0;
} else {
nextYawVel = 0x100;
}
if ((gPlayer1Controller->stickX != 0.f || gPlayer1Controller->stickY != 0.f) != 0) {
nextYawVel = 0x20;
}
} else {
if (sCSideButtonYaw < 0) {
yaw += 0x200;
}
if (sCSideButtonYaw > 0) {
yaw -= 0x200;
}
camera_approach_s16_symmetric_bool(&sCSideButtonYaw, 0, 0x100);
nextYawVel = 0;
}
sYawSpeed = 0x400;
xzDist = calc_hor_dist(sMarioCamState->pos, c->pos);
if (sStatusFlags & CAM_FLAG_BEHIND_MARIO_POST_DOOR) {
if (xzDist >= 250) {
sStatusFlags &= ~CAM_FLAG_BEHIND_MARIO_POST_DOOR;
}
if (ABS((sMarioCamState->faceAngle[1] - yaw) / 2) < 0x1800) {
sStatusFlags &= ~CAM_FLAG_BEHIND_MARIO_POST_DOOR;
yaw = sCameraYawAfterDoorCutscene + DEGREES(180);
dist = 800.f;
sStatusFlags |= CAM_FLAG_BLOCK_SMOOTH_MOVEMENT;
}
} else if (xzDist < 250) {
// Turn rapidly if very close to mario
c->pos[0] += (250 - xzDist) * sins(yaw);
c->pos[2] += (250 - xzDist) * coss(yaw);
if (sCSideButtonYaw == 0) {
nextYawVel = 0x1000;
sYawSpeed = 0;
vec3f_get_dist_and_angle(sMarioCamState->pos, c->pos, &dist, &pitch, &yaw);
}
closeToMario |= 1;
}
if (-16 < gPlayer1Controller->stickY) {
c->yaw = yaw;
}
calc_y_to_curr_floor(&posHeight, 1, 200, &focHeight, 0.9f, 200);
vec3f_copy(cPos, c->pos);
avoidStatus = rotate_camera_around_walls(c, cPos, &avoidYaw, 0x600);
// If a wall is blocking the view of mario, then rotate in the calculated direction
if (avoidStatus == 3) {
unusedFreeRoamWallYaw = avoidYaw;
sAvoidYawVel = yaw;
sStatusFlags |= CAM_FLAG_COLLIDED_WITH_WALL;
//! Does nothing
vec3f_get_dist_and_angle(sMarioCamState->pos, cPos, &xzDist, &tempPitch, &tempYaw);
// Rotate to avoid the wall
approach_s16_asymptotic_bool(&yaw, avoidYaw, 10);
//! Does nothing
vec3f_set_dist_and_angle(sMarioCamState->pos, cPos, xzDist, tempPitch, tempYaw);
sAvoidYawVel = (sAvoidYawVel - yaw) / 0x100;
} else {
if (gMarioStates[0].forwardVel == 0.f) {
if (sStatusFlags & CAM_FLAG_COLLIDED_WITH_WALL) {
if ((yawGoal - yaw) / 0x100 >= 0) {
yawDir = -1;
} else {
yawDir = 1;
}
if ((sAvoidYawVel > 0 && yawDir > 0) || (sAvoidYawVel < 0 && yawDir < 0)) {
yawVel = nextYawVel;
}
} else {
yawVel = nextYawVel;
}
} else {
if (nextYawVel == 0x1000) {
yawVel = nextYawVel;
}
sStatusFlags &= ~CAM_FLAG_COLLIDED_WITH_WALL;
}
// If a wall is near the camera, turn twice as fast
if (avoidStatus != 0) {
yawVel += yawVel;
}
// ...Unless the camera already rotated from being close to mario
if ((closeToMario & 1) && avoidStatus != 0) {
yawVel = 0;
}
if (yawVel != 0 && get_dialog_id() == -1) {
camera_approach_s16_symmetric_bool(&yaw, yawGoal, yawVel);
}
}
// Only zoom out if not obstructed by walls and lakitu hasn't collided with any
if (avoidStatus == 0 && !(sStatusFlags & CAM_FLAG_COLLIDED_WITH_WALL)) {
approach_f32_asymptotic_bool(&dist, zoomDist - 100.f, 0.05f);
}
vec3f_set_dist_and_angle(sMarioCamState->pos, cPos, dist, pitch, yaw);
cPos[1] += posHeight + 125.f;
// Move the camera away from walls and set the collision flag
if (collide_with_walls(cPos, 10.f, 80.f) != 0) {
sStatusFlags |= CAM_FLAG_COLLIDED_WITH_WALL;
}
c->focus[0] = sMarioCamState->pos[0];
c->focus[1] = sMarioCamState->pos[1] + 125.f + focHeight;
c->focus[2] = sMarioCamState->pos[2];
marioFloorHeight = 125.f + sMarioGeometry.currFloorHeight;
marioFloor = sMarioGeometry.currFloor;
camFloorHeight = find_floor(cPos[0], cPos[1] + 50.f, cPos[2], &cFloor) + 125.f;
for (scale = 0.1f; scale < 1.f; scale += 0.2f) {
scale_along_line(tempPos, cPos, sMarioCamState->pos, scale);
tempFloorHeight = find_floor(tempPos[0], tempPos[1], tempPos[2], &tempFloor) + 125.f;
if (tempFloor != NULL && tempFloorHeight > marioFloorHeight) {
marioFloorHeight = tempFloorHeight;
marioFloor = tempFloor;
}
}
// Lower the camera in mario mode
if (sSelectionFlags & CAM_MODE_MARIO_ACTIVE) {
marioFloorHeight -= 35.f;
camFloorHeight -= 35.f;
c->focus[1] -= 25.f;
}
// If there's water below the camera, decide whether to keep the camera above the water surface
waterHeight = find_water_level(cPos[0], cPos[2]);
if (waterHeight != -11000.f) {
waterHeight += 125.f;
distFromWater = waterHeight - marioFloorHeight;
if (!(gCameraMovementFlags & CAM_MOVE_METAL_BELOW_WATER)) {
if (distFromWater > 800.f && (sMarioCamState->action & ACT_FLAG_METAL_WATER)) {
gCameraMovementFlags |= CAM_MOVE_METAL_BELOW_WATER;
}
} else {
if (distFromWater < 400.f || !(sMarioCamState->action & ACT_FLAG_METAL_WATER)) {
gCameraMovementFlags &= ~CAM_MOVE_METAL_BELOW_WATER;
}
}
// If not wearing the metal cap, always stay above
if (!(gCameraMovementFlags & CAM_MOVE_METAL_BELOW_WATER) && camFloorHeight < waterHeight) {
camFloorHeight = waterHeight;
}
} else {
gCameraMovementFlags &= ~CAM_MOVE_METAL_BELOW_WATER;
}
cPos[1] = camFloorHeight;
vec3f_copy(tempPos, cPos);
tempPos[1] -= 125.f;
if (marioFloor != NULL && camFloorHeight <= marioFloorHeight) {
avoidStatus = is_range_behind_surface(c->focus, tempPos, marioFloor, 0, -1);
if (avoidStatus != 1 && ceilHeight > marioFloorHeight) {
camFloorHeight = marioFloorHeight;
}
}
posHeight = 0.f;
if (c->mode == CAMERA_MODE_FREE_ROAM) {
if (gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) {
posHeight = 375.f;
if (gCurrLevelArea == AREA_SSL_PYRAMID) {
posHeight /= 2;
}
} else {
posHeight = 100.f;
}
}
if ((gCameraMovementFlags & CAM_MOVE_ZOOMED_OUT) && (sSelectionFlags & CAM_MODE_MARIO_ACTIVE)) {
posHeight = 610.f;
if (gCurrLevelArea == AREA_SSL_PYRAMID || gCurrLevelNum == LEVEL_CASTLE) {
posHeight /= 2;
}
}
// Make lakitu fly above the gas
gasHeight = find_poison_gas_level(cPos[0], cPos[2]);
if (gasHeight != -11000.f) {
if ((gasHeight += 130.f) > c->pos[1]) {
c->pos[1] = gasHeight;
}
}
if (sMarioCamState->action & ACT_FLAG_HANGING || sMarioCamState->action == ACT_RIDING_HOOT) {
camFloorHeight = sMarioCamState->pos[1] + 400.f;
if (c->mode == CAMERA_MODE_FREE_ROAM) {
camFloorHeight -= 100.f;
}
ceilHeight = 20000.f;
vec3f_copy(c->focus, sMarioCamState->pos);
}
if (sMarioCamState->action & ACT_FLAG_ON_POLE) {
camFloorHeight = gMarioStates[0].usedObj->oPosY + 125.f;
if (sMarioCamState->pos[1] - 100.f > camFloorHeight) {
camFloorHeight = sMarioCamState->pos[1] - 100.f;
}
ceilHeight = 20000.f;
vec3f_copy(c->focus, sMarioCamState->pos);
}
if (camFloorHeight != -11000.f) {
camFloorHeight += posHeight;
approach_camera_height(c, camFloorHeight, 20.f);
}
c->pos[0] = cPos[0];
c->pos[2] = cPos[2];
cPos[0] = gLakituState.goalPos[0];
cPos[1] = c->pos[1];
cPos[2] = gLakituState.goalPos[2];
vec3f_get_dist_and_angle(cPos, c->pos, &dist, &tempPitch, &tempYaw);
// Prevent the camera from lagging behind too much
if (dist > 50.f) {
dist = 50.f;
vec3f_set_dist_and_angle(cPos, c->pos, dist, tempPitch, tempYaw);
}
if (sMarioGeometry.currFloorType != SURFACE_DEATH_PLANE) {
vec3f_get_dist_and_angle(c->focus, c->pos, &dist, &tempPitch, &tempYaw);
if (dist > zoomDist) {
dist = zoomDist;
vec3f_set_dist_and_angle(c->focus, c->pos, dist, tempPitch, tempYaw);
}
}
if (ceilHeight != 20000.f) {
if (c->pos[1] > (ceilHeight -= 150.f)
&& (avoidStatus = is_range_behind_surface(c->pos, sMarioCamState->pos, ceil, 0, -1)) == 1) {
c->pos[1] = ceilHeight;
}
}
if (gCurrLevelArea == AREA_WDW_TOWN) {
yaw = clamp_positions_and_find_yaw(c->pos, c->focus, 2254.f, -3789.f, 3790.f, -2253.f);
}
return yaw;
}
/**
* The default camera mode
* Used by close and free roam modes
*/
void mode_default_camera(struct Camera *c) {
set_fov_function(CAM_FOV_DEFAULT);
c->nextYaw = update_default_camera(c);
pan_ahead_of_player(c);
}
/**
* The mode used by close and free roam
*/
void mode_lakitu_camera(struct Camera *c) {
gCameraZoomDist = 800.f;
mode_default_camera(c);
}
/**
* When no other mode is active and the current R button mode is mario
*/
void mode_mario_camera(struct Camera *c) {
gCameraZoomDist = 350.f;
mode_default_camera(c);
}
/**
* Rotates the camera around the spiral staircase.
*/
s32 update_spiral_stairs_camera(struct Camera *c, Vec3f focus, Vec3f pos) {
UNUSED s16 unused1;
/// The returned yaw
s16 camYaw;
// unused
s16 focPitch;
/// The focus (mario)'s yaw around the stairs
s16 focYaw;
// unused
s16 posPitch;
/// The camera's yaw around the stairs
s16 posYaw;
UNUSED s32 unused2;
Vec3f cPos;
Vec3f checkPos;
struct Surface *floor;
// unused
f32 dist;
f32 focusHeight;
f32 floorHeight;
f32 focY;
handle_c_button_movement(c);
// Set base pos to the center of the staircase
vec3f_set(sFixedModeBasePosition, -1280.f, 614.f, 1740.f);
// Focus on mario, and move the focus up the staircase with him
calc_y_to_curr_floor(&focusHeight, 1.f, 200.f, &focusHeight, 0.9f, 200.f);
focus[0] = sMarioCamState->pos[0];
focY = sMarioCamState->pos[1] + 125.f + focusHeight;
focus[2] = sMarioCamState->pos[2];
vec3f_copy(cPos, pos);
vec3f_get_dist_and_angle(sFixedModeBasePosition, focus, &dist, &focPitch, &focYaw);
vec3f_get_dist_and_angle(sFixedModeBasePosition, cPos, &dist, &posPitch, &posYaw);
sSpiralStairsYawOffset = posYaw - focYaw;
// posYaw will change if mario is more than 90 degrees around the stairs, relative to the camera
if (sSpiralStairsYawOffset < DEGREES(-90)) {
sSpiralStairsYawOffset = DEGREES(-90);
}
if (sSpiralStairsYawOffset > DEGREES(90)) {
sSpiralStairsYawOffset = DEGREES(90);
}
focYaw += sSpiralStairsYawOffset;
posYaw = focYaw;
//! @bug unnecessary
camera_approach_s16_symmetric_bool(&posYaw, focYaw, 0x1000);
vec3f_set_dist_and_angle(sFixedModeBasePosition, cPos, 300.f, 0, posYaw);
// Move the camera's y coord up/down the staircase
checkPos[0] = focus[0] + (cPos[0] - focus[0]) * 0.7f;
checkPos[1] = focus[1] + (cPos[1] - focus[1]) * 0.7f + 300.f;
checkPos[2] = focus[2] + (cPos[2] - focus[2]) * 0.7f;
floorHeight = find_floor(checkPos[0], checkPos[1] + 50.f, checkPos[2], &floor);
if (floorHeight != -11000.f) {
if (floorHeight < sMarioGeometry.currFloorHeight) {
floorHeight = sMarioGeometry.currFloorHeight;
}
pos[1] = approach_f32(pos[1], (floorHeight += 125.f), 30.f, 30.f);
}
camera_approach_f32_symmetric_bool(&focus[1], focY, 30.f);
pos[0] = cPos[0];
pos[2] = cPos[2];
camYaw = calculate_yaw(focus, pos);
return camYaw;
}
/**
* The mode used in the spiral staircase in the castle
*/
void mode_spiral_stairs_camera(struct Camera *c) {
c->nextYaw = update_spiral_stairs_camera(c, c->focus, c->pos);
}
s32 update_slide_or_0f_camera(UNUSED struct Camera *c, Vec3f focus, Vec3f pos) {
s16 yaw = sMarioCamState->faceAngle[1] + sModeOffsetYaw + DEGREES(180);
focus_on_mario(focus, pos, 125.f, 125.f, 800.f, 5461, yaw);
return sMarioCamState->faceAngle[1];
}
static UNUSED void unused_mode_0f_camera(struct Camera *c) {
if (gPlayer1Controller->buttonPressed & U_CBUTTONS) {
gCameraMovementFlags |= CAM_MOVE_C_UP_MODE;
}
c->nextYaw = update_slide_camera(c);
}
/**
* Slide/hoot mode.
* In this mode, the camera is always at the back of mario, because mario generally only moves forward.
*/
void mode_slide_camera(struct Camera *c) {
if (sMarioGeometry.currFloorType == SURFACE_CLOSE_CAMERA ||
sMarioGeometry.currFloorType == SURFACE_NO_CAM_COL_SLIPPERY) {
mode_lakitu_camera(c);
} else {
if (gPlayer1Controller->buttonPressed & U_CBUTTONS) {
gCameraMovementFlags |= CAM_MOVE_C_UP_MODE;
}
c->nextYaw = update_slide_camera(c);
}
}
void store_lakitu_cam_info_for_c_up(struct Camera *c) {
vec3f_copy(sCameraStoreCUp.pos, c->pos);
vec3f_sub(sCameraStoreCUp.pos, sMarioCamState->pos);
// Only store the y value, and as an offset from mario, for some reason
vec3f_set(sCameraStoreCUp.focus, 0.f, c->focus[1] - sMarioCamState->pos[1], 0.f);
}
/**
* Start C-Up mode. The actual mode change is handled in update_mario_inputs() in mario.c
*
* @see update_mario_inputs
*/
s32 set_mode_c_up(struct Camera *c) {
if (!(gCameraMovementFlags & CAM_MOVE_C_UP_MODE)) {
gCameraMovementFlags |= CAM_MOVE_C_UP_MODE;
store_lakitu_cam_info_for_c_up(c);
sCameraSoundFlags &= ~CAM_SOUND_C_UP_PLAYED;
return 1;
}
return 0;
}
/**
* Zoom the camera out of C-Up mode, avoiding moving into a wall, if possible, by searching for an open
* direction.
*/
s32 exit_c_up(struct Camera *c) {
struct Surface *surface;
Vec3f checkFoc;
Vec3f curPos;
// Variables for searching for an open direction
s32 searching = 0;
/// The current sector of the circle that we are checking
s32 sector;
f32 ceilHeight;
f32 floorHeight;
f32 curDist;
f32 d;
s16 curPitch;
s16 curYaw;
s16 checkYaw = 0;
Vec3f storePos; // unused
Vec3f storeFoc; // unused
if ((gCameraMovementFlags & CAM_MOVE_C_UP_MODE) && !(gCameraMovementFlags & CAM_MOVE_STARTED_EXITING_C_UP)) {
// Copy the stored pos and focus. This is unused.
vec3f_copy(storePos, sCameraStoreCUp.pos);
vec3f_add(storePos, sMarioCamState->pos);
vec3f_copy(storeFoc, sCameraStoreCUp.focus);
vec3f_add(storeFoc, sMarioCamState->pos);
vec3f_copy(checkFoc, c->focus);
checkFoc[0] = sMarioCamState->pos[0];
checkFoc[2] = sMarioCamState->pos[2];
vec3f_get_dist_and_angle(checkFoc, c->pos, &curDist, &curPitch, &curYaw);
vec3f_copy(curPos, c->pos);
curDist = 80.f;
// Search for an open direction to zoom out in, if the camera is changing to close, free roam,
// or spiral-stairs mode
if (sModeInfo.<