A deep dive into SM64’s camera system: the Camera and LakituState structs, all camera modes, C-button input, CameraTriggers, and the cutscene framework.
Use this file to discover all available pages before exploring further.
The camera system in SM64 is one of the largest single files in the codebase (src/game/camera.c, over 11,500 lines). It manages everything from the mundane — following Mario around a hill — to the complex: multi-shot cutscenes with spline-interpolated paths, room-transition triggers, FOV shakes, and the handheld-camera effect. The system is designed around a conceptual “Lakitu” entity: a smoothly flying camera operator who tracks a goal position and focus, always approaching them asymptotically rather than snapping instantly.
// From the file header comment in camera.c:// +X points right.// +Y points up.// +Z points out of the screen.//// pitch: rotation about the X-axis, measured from +Y.// Bounded to +-0x4000 (90 degrees).// yaw: rotation about the Y-axis, measured from (absolute) +Z.// Positive yaw rotates clockwise, towards +X.// roll: rotation about the Z-axis (flipped: positive = counterclockwise world).
Angles are stored as s16 values where 0x10000 = 360°. The macro DEGREES(x) converts human-readable degrees:
struct Camera { u8 mode; // active camera mode (CAMERA_MODE_*) u8 defMode; // default mode to return to after special modes s16 yaw; // direction Mario moves (focus→pos angle, NOT pos→focus!) Vec3f focus; // target look-at point Vec3f pos; // camera position Vec3f unusedVec1; f32 areaCenX; // area rotation centre (x), e.g. the BoB hilltop f32 areaCenZ; // area rotation centre (z) u8 cutscene; // currently playing cutscene ID (0 = none) u8 filler1[8]; s16 nextYaw; u8 filler2[40]; u8 doorStatus; // DOOR_DEFAULT / DOOR_LEAVING_SPECIAL / DOOR_ENTER_LOBBY f32 areaCenY; // area centre y, used only when paused};
camera->yaw is the angle from focus to pos, not the camera’s forward direction. It is effectively the camera’s yaw flipped by 180°. Use vec3f_get_dist_and_angle() if you need the true forward yaw.
While Camera represents the goal, LakituState is what the renderer actually uses. Lakitu smoothly flies towards his goal each frame:
struct LakituState { Vec3f curFocus; // current focus (tracks goalFocus) Vec3f curPos; // current position (tracks goalPos) Vec3f goalFocus; // target focus this frame Vec3f goalPos; // target position this frame // … u8 mode; // copy of Camera.mode u8 defMode; // … Vec3s shakeMagnitude; // angular offset added by shake effects s16 shakePitchPhase, shakePitchVel, shakePitchDecay; // … s16 roll; // screen roll for rendering s16 yaw; // copy of Camera.yaw s16 nextYaw; Vec3f focus; // ACTUAL rendered focus Vec3f pos; // ACTUAL rendered position // … shake yaw/roll variables … f32 focHSpeed; // horizontal speed approaching goalFocus (default 0.8) f32 focVSpeed; // vertical speed approaching goalFocus (default 0.3) f32 posHSpeed; // horizontal speed approaching goalPos (default 0.3) f32 posVSpeed; // vertical speed approaching goalPos (default 0.3) // …};
Every frame, update_lakitu() moves curPos and curFocus toward goalPos and goalFocus using the H/V speed multipliers, then copies the result into LakituState.pos and LakituState.focus. These are what geo_camera_main reads to build the view matrix.
Used by transition_next_state() to smoothly interpolate Lakitu between his current and goal positions over a fixed number of frames.
CutsceneSplinePoint — camera path waypoint
struct CutsceneSplinePoint { s8 index; // -1 ends the spline u8 speed; // controls traversal time per segment Vec3s point;};
Arrays of these define the camera paths used in star-collection dances, boss intros, and the ending sequence. move_point_along_spline() evaluates the Catmull-Rom spline.
CameraTrigger — spatial camera event zone
struct CameraTrigger { s8 area; // area index, or -1 for level-wide CameraEvent event; s16 centerX, centerY, centerZ; s16 boundsX, boundsY, boundsZ; s16 boundsYaw; // rotates the bounding box};
While Mario is inside a trigger’s axis-aligned box, its event function is called every frame. These control fixed-camera angles in Castle rooms, parallel-tracking segments, and boss-fight framing.
CameraFOVStatus — FOV and shake
struct CameraFOVStatus { u8 fovFunc; // which preset function sets the base FOV f32 fov; // current FOV in degrees f32 fovOffset; // shake offset added to fov u32 unusedIsSleeping; f32 shakeAmplitude; // range in degrees s16 shakePhase; s16 shakeSpeed; s16 decay;};
ParallelTrackingPoint — rail camera path
struct ParallelTrackingPoint { s16 startOfPath; Vec3f pos; f32 distThresh; // how far Mario can stray before camera moves f32 zoom; // fraction of the way from rail to Mario};
Used by update_parallel_tracking_camera() in Rainbow Ride and similar linear paths.
The active mode is stored in Camera.mode and LakituState.mode. Mode changes go through set_camera_mode(), which records framesLeft for a smooth transition.
Constant
Value
Description
CAMERA_MODE_NONE
0x00
Uninitialized / cutscene override
CAMERA_MODE_RADIAL
0x01
Standard follow-cam; rotates around areaCenX/Z
CAMERA_MODE_OUTWARD_RADIAL
0x02
Same but pushes outward from the area centre
CAMERA_MODE_BEHIND_MARIO
0x03
Locks behind Mario’s back
CAMERA_MODE_CLOSE
0x04
Tight follow; used in Castle interiors and BBH
CAMERA_MODE_C_UP
0x06
First-person look-around (hold C-Up)
CAMERA_MODE_WATER_SURFACE
0x08
Follows Mario at water-surface height
CAMERA_MODE_SLIDE_HOOT
0x09
Slide and owl-hoot modes
CAMERA_MODE_INSIDE_CANNON
0x0A
Locked inside cannon
CAMERA_MODE_BOSS_FIGHT
0x0B
Frames both Mario and the boss
CAMERA_MODE_PARALLEL_TRACKING
0x0C
Rail camera along a ParallelTrackingPoint path
CAMERA_MODE_FIXED
0x0D
Locked to a single world position
CAMERA_MODE_8_DIRECTIONS
0x0E
8-direction snap (Bowser courses, Rainbow Ride)
CAMERA_MODE_FREE_ROAM
0x10
Smooth free-orbit (default outside special areas)
CAMERA_MODE_SPIRAL_STAIRS
0x11
Hugs the staircase in the Castle
set_camera_mode() signature:
void set_camera_mode(struct Camera *c, s16 mode, s16 frames);
The frames parameter controls how many frames the interpolation from the old position to the new mode’s goal takes.
// Called once per level load to zero out and set up the Camera structvoid init_camera(struct Camera *c);// Called every frame from the game loop; dispatches to the active modevoid update_camera(struct Camera *c);// Hard reset (used after level transitions)void reset_camera(struct Camera *c);// Set the camera to use "Mario cam" (free/8-dir) vs "Lakitu cam" (fixed angles)void select_mario_cam_mode(void);
// Instantly displace the camera (used when crossing area boundaries)void warp_camera(f32 displacementX, f32 displacementY, f32 displacementZ);// Move Lakitu smoothly between old and new positions over N framesvoid transition_next_state(struct Camera *c, s16 frames);// Compute the next frame of Lakitu's position from goal deltass16 next_lakitu_state(Vec3f newPos, Vec3f newFoc, Vec3f curPos, Vec3f curFoc, Vec3f oldPos, Vec3f oldFoc, s16 yaw);
Two functions are exposed to the geo layout system as GraphNodeFunc callbacks:
// Called every frame from the GraphNodeCamera; updates the view matrixGfx *geo_camera_main(s32 callContext, struct GraphNode *g, void *context);// Called every frame from the GraphNodePerspective; adjusts FOV dynamicallyGfx *geo_camera_fov(s32 callContext, struct GraphNode *g, void *context);
These are referenced directly in level geo layouts:
// Read C-button presses and translate them to camera rotation flagss32 find_c_buttons_pressed(u16 currentState, u16 buttonsPressed, u16 buttonsDown);void handle_c_button_movement(struct Camera *c);// Toggle between Mario-controlled (free/8-dir) and fixed-angle cameras32 set_cam_angle(s32 mode); // CAM_ANGLE_MARIO or CAM_ANGLE_LAKITUs32 cam_select_alt_mode(s32 angle);
Movement flags (gCameraMovementFlags) track the current C-button state:
Cutscenes are sequences of struct Cutscene entries — each pairing a CutsceneShot function with a duration in frames:
struct Cutscene { CutsceneShot shot; // function called every frame during this shot s16 duration;};// CUTSCENE_LOOP runs the shot indefinitely; CUTSCENE_STOP ends the cutscene#define CUTSCENE_LOOP 0x7FFF#define CUTSCENE_STOP 0x8000
play_cutscene() selects the right Cutscene[] array based on Camera.cutscene and steps through shots frame by frame. Within a shot, helper functions schedule one-shot events:
// Run `event` only during frames [start, end] of the current shots32 cutscene_event(CameraEvent event, struct Camera *c, s16 start, s16 end);// Spawn an object at a specific frame of the cutscenes32 cutscene_spawn_obj(u32 obj, s16 frame);
Returns 1 when the end of the spline is reached (index = -1 sentinel). The speed field of each control point controls how many frames it takes to traverse that segment.
Modes include HAND_CAM_SHAKE_CUTSCENE, HAND_CAM_SHAKE_HIGH, HAND_CAM_SHAKE_STAR_DANCE, etc. The effect evaluates a HandheldShakePoint spline that introduces small random perturbations to the rendered view.
The player toggles between Mario cam and Lakitu cam with the R button. sSelectionFlags tracks whether Mario or fixed camera was most recently selected:
camera_course_processing() is the main per-frame dispatcher for area-specific CameraTrigger arrays. If you want to add a fixed-angle zone or parallel-tracking segment to a custom level, add a CameraTrigger entry to the level’s trigger table and set mode via set_camera_mode() inside the event function.