fs2port/port/include/camera.h
2026-05-14 10:03:23 -05:00

97 lines
3.8 KiB
C

// Camera state and world-to-camera transform.
//
// Coordinate conventions (right-handed, looks down +Z):
// +X right (in camera frame)
// +Y up
// +Z forward (out of the cockpit)
//
// Position uses the same Q16.16 world-unit convention as `AircraftT`
// (low 16 bits fractional). Forward speed is Q8.8 world-units / frame.
// Orientation is stored as byte angles (256 == full turn). The
// rotation matrix is Q1.15 (matches `math6502Sin/Cos` output), so a
// world->camera transform is a 3x3 dot product of int16 against
// Q16.16 deltas, normalised by `>> 15`.
#ifndef CAMERA_H
#define CAMERA_H
#include <stdint.h>
#include "types.h"
#define CAM_POS_FRACT_BITS 16
#define CAM_POS_FRACT_ONE (1 << CAM_POS_FRACT_BITS)
#define CAM_RATE_FRACT_BITS 8
#define CAM_RATE_FRACT_ONE (1 << CAM_RATE_FRACT_BITS)
#define CAM_ROT_FRACT_BITS 15
#define CAM_ROT_ONE (1 << CAM_ROT_FRACT_BITS)
static inline int32_t metresFromQ1616(int32_t v_q1616) {
return v_q1616 >> CAM_POS_FRACT_BITS;
}
static inline int32_t q1616FromMetres(int32_t m) {
return m * CAM_POS_FRACT_ONE;
}
// Convert FS2 byte angle (0..255 == 0..359 degrees) to degrees.
static inline int16_t byteAngleToDegrees(uint8_t angle) {
return (int16_t)(((int32_t)angle * 360) / 256);
}
typedef struct CameraT {
int32_t worldX; // Q16.16 world units
int32_t worldY;
int32_t worldZ;
uint8_t pitch; // X-axis rotation (nose up/down)
uint8_t bank; // Z-axis rotation (roll)
uint8_t yaw; // Y-axis rotation (heading)
// Sub-byte angle precision matching chunk5's 16-bit
// representation at $6C/$6E/$70. The combined 16-bit angle
// is `((pitch << 8) | pitchFine)` etc. -- chunk5SetupView-
// Projection uses these full 16-bit values to derive the
// matrix. MAME's Meigs boot has $6C/$6D=-109 (= -0.6 deg),
// which is finer than the 1/256-of-a-circle 8-bit pitch
// can express on its own.
uint8_t pitchFine;
uint8_t bankFine;
uint8_t yawFine;
// chunk5 ViewDirection ($0A70). Multiplied by 16 inside
// SetupViewProjection's L6155 to bias the matrix's yaw.
// MAME's Meigs boot has $0A70 = $0F.
uint8_t viewDirection;
int16_t forwardSpeed; // Q8.8 world units per frame
int16_t rot[3][3]; // Q1.15 world -> camera rotation matrix (R^T)
// chunk5 SetupViewProjection lays $78..$89 out as R (camera-
// to-world), NOT R^T. sceneryAttachCamera mirrors this matrix
// into writableRam so downstream chunk5 paths (notably L631D
// section base) see the same shape they would on the
// original. Same data as `rot` but transposed.
int16_t rotChunk5[3][3];
} CameraT;
void cameraInit(CameraT *cam);
// Recompute the rotation matrix from pitch/bank/yaw. Call once after
// any orientation change.
void cameraUpdate(CameraT *cam);
// Transform a world-space point into camera space. Caller supplies
// a fresh `CameraT` (already updated this frame). All coords are
// Q16.16 world units.
void cameraTransform(const CameraT *cam, int32_t wx_q1616, int32_t wy_q1616, int32_t wz_q1616, int32_t *cx_q1616, int32_t *cy_q1616, int32_t *cz_q1616);
// Move the camera forward by `forwardSpeed` along its current heading.
void cameraStep(CameraT *cam);
// Decompose the camera's 3x3 rotation matrix into the 2x3 form chunk5
// uses (XZ in the world plane -> 3D camera-space). Each matrix entry
// is scaled to int8_t with $7F == 1.0 so MultiplyXY's int8 inputs see
// the right magnitude. Y (altitude) is handled per-section by the
// scenery $0D Header opcode and so is excluded from this matrix; the
// world driver handles altitude through the section-base instead.
void cameraGet2x3Matrix(const CameraT *cam, int8_t outRowX[3], int8_t outRowZ[3]);
#endif