// 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 #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