172 lines
7.3 KiB
C
172 lines
7.3 KiB
C
// 3D vertex pipeline that mirrors FS2's chunk5 polygon math.
|
|
//
|
|
// The original 6502 implementation lives in chunk5.s:
|
|
//
|
|
// L7EBC -- per-vertex coord transform (auto-scale +
|
|
// rotation matrix)
|
|
// ClassifyVertex1/2 -- 6-plane outcode generation
|
|
// ProjectV1ToScreen, -- perspective divide -> screen pixel
|
|
// ProjectV2ToScreen
|
|
// PerspectiveDivide -- shift-and-subtract 16/16 divide
|
|
// EmitPrimaryVertex -- append a vertex to the 60-slot pool
|
|
//
|
|
// This port keeps the same precision (signed 16-bit world deltas, 8-bit
|
|
// rotation matrix, 16/16 perspective divide) and the same outcode bit
|
|
// assignments, so the algorithmic results match the 6502 game value-
|
|
// for-value modulo the LSB of MultiplyXY's 7x7 truncation.
|
|
//
|
|
// Outcode bit layout (from ClassifyVertex2 at chunk5 line 2673):
|
|
// bit 7 ($80) -- z negative (behind camera)
|
|
// bit 6 ($40) -- x + z < 0 (right of frustum)
|
|
// bit 5 ($20) -- z - x < 0 (left of frustum)
|
|
// bit 4 ($10) -- y + z < 0 (below frustum)
|
|
// bit 3 ($08) -- z - y < 0 (above frustum)
|
|
|
|
#ifndef SCENERY_PROJECTION_H
|
|
#define SCENERY_PROJECTION_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
#define SCENERY_OUTCODE_BEHIND 0x80
|
|
#define SCENERY_OUTCODE_RIGHT 0x40
|
|
#define SCENERY_OUTCODE_LEFT 0x20
|
|
#define SCENERY_OUTCODE_BOTTOM 0x10
|
|
#define SCENERY_OUTCODE_TOP 0x08
|
|
|
|
#define SCENERY_VERTEX_POOL_CAP 60
|
|
|
|
|
|
// One slot in the primary vertex pool. Six bytes are stored per vertex
|
|
// in chunk5 (six parallel arrays at L0AB8/AF8/B38/B78/BB8/BF8); we
|
|
// pack them into a single struct for cache behaviour. Components are
|
|
// camera-space x/y/z in 16-bit signed, plus a Cohen-Sutherland outcode.
|
|
typedef struct SceneryVertexT {
|
|
int16_t x; // L0AB8/L0AF8 = lo/hi of camera-space x
|
|
int16_t y; // L0B38/L0B78 = lo/hi of camera-space y
|
|
int16_t z; // L0BB8/L0BF8 = lo/hi of camera-space z
|
|
uint8_t outcode; // 6-plane mask (see SCENERY_OUTCODE_*)
|
|
} SceneryVertexT;
|
|
|
|
|
|
// Per-frame projection state. Mirrors the chunk5 zero-page variables
|
|
// the polygon pipeline reads:
|
|
// $66/$67 = camera world X (eyepoint)
|
|
// $6A/$6B = camera world Z
|
|
// $79/$7B/$7D = first row of the 2x3 rotation matrix (XZ -> camX)
|
|
// $85/$87/$89 = second row of the 2x3 rotation matrix (XZ -> camY/Z)
|
|
// $4A/$4D/$50 = section-base contribution to camX/camY/camZ
|
|
// (set by the coord-frame opcode $0D, accounts for the
|
|
// vertical/altitude term)
|
|
// $2F = current zoom-detail counter (auto-scale shift count)
|
|
typedef struct SceneryProjStateT {
|
|
int16_t camX; // $66/$67
|
|
int16_t camZ; // $6A/$6B
|
|
int8_t matRow1[3]; // $79, $7B, $7D
|
|
int8_t matRow2[3]; // $85, $87, $89
|
|
int16_t baseX; // $4A
|
|
int16_t baseY; // $4D
|
|
int16_t baseZ; // $50
|
|
uint8_t zoomShift; // $2F (init $40, decrements as we shift)
|
|
} SceneryProjStateT;
|
|
|
|
|
|
// Two "current" vertex slots, mirroring chunk5's $CB..$D2 (vertex 1)
|
|
// and $D4..$DB (vertex 2). The vertex-emit family writes into these
|
|
// before deciding whether to project, classify, or push into the pool.
|
|
typedef struct SceneryCurrentT {
|
|
SceneryVertexT v1; // $CB..$D0 (+ $CA outcode, $D1/$D2 screen)
|
|
SceneryVertexT v2; // $D4..$D9 (+ $D3 outcode, $DA/$DB screen)
|
|
int16_t v1ScreenX; // $D1
|
|
int16_t v1ScreenY; // $D2
|
|
int16_t v2ScreenX; // $DA
|
|
int16_t v2ScreenY; // $DB
|
|
// Running coord accumulators that L7EBC writes into ($18/$1A,
|
|
// $1B/$1D, $1E/$20). Three signed 16-bit values plus a sign-
|
|
// extension byte each; we collapse to int32 for the
|
|
// intermediate sum and snap back to int16 on store.
|
|
int16_t accX;
|
|
int16_t accY;
|
|
int16_t accZ;
|
|
// Polygon outcode AND-accumulator at $D3 (zero -> all vertices
|
|
// share an offscreen plane, polygon culled).
|
|
uint8_t polygonOutcode;
|
|
// Vertex-pool head ($B5).
|
|
uint8_t poolCount;
|
|
} SceneryCurrentT;
|
|
|
|
|
|
typedef struct SceneryPipelineT {
|
|
SceneryProjStateT proj;
|
|
SceneryCurrentT cur;
|
|
SceneryVertexT pool[SCENERY_VERTEX_POOL_CAP];
|
|
} SceneryPipelineT;
|
|
|
|
|
|
// Reset the vertex pool and outcode accumulator. Call at the top of
|
|
// each scenery frame and whenever opcode $2F (SceneryOpResetState)
|
|
// fires.
|
|
void sceneryPipelineReset(SceneryPipelineT *pipe);
|
|
|
|
|
|
// Set the camera world position and the 2x3 rotation matrix. Called
|
|
// by the world driver once per frame, before sceneryRun walks the
|
|
// stream. Matrix entries are signed 8-bit (see chunk5 $79..$89).
|
|
void sceneryPipelineSetCamera(SceneryPipelineT *pipe, int16_t worldX, int16_t worldZ);
|
|
void sceneryPipelineSetMatrix(SceneryPipelineT *pipe, const int8_t row1[3], const int8_t row2[3]);
|
|
void sceneryPipelineSetBase(SceneryPipelineT *pipe, int16_t bx, int16_t by, int16_t bz);
|
|
|
|
|
|
// L7EBC: read 4 stream bytes (XZ pair, signed 16-bit each), subtract
|
|
// camera XZ, auto-scale, multiply by the 2x3 rotation matrix, add to
|
|
// section base, store into target slot ($CB..$D0 or $D4..$D9).
|
|
//
|
|
// Returns the number of stream bytes consumed (always 4), so the
|
|
// caller can advance.
|
|
int sceneryProjectStreamVertex(SceneryPipelineT *pipe, const uint8_t *streamPlus1, SceneryVertexT *outSlot);
|
|
|
|
|
|
// Same math as sceneryProjectStreamVertex but takes the world XZ pair
|
|
// directly (caller already has decoded values). Used by the world
|
|
// driver to push hardcoded vertex data through the same pipeline a
|
|
// real scenery byte stream would.
|
|
void sceneryProjectXZ(SceneryPipelineT *pipe, int16_t worldX, int16_t worldZ, SceneryVertexT *outSlot);
|
|
|
|
|
|
// ClassifyVertex2-style outcode for a camera-space vertex. Pure
|
|
// function; reads only the vertex itself.
|
|
uint8_t sceneryClassifyVertex(const SceneryVertexT *v);
|
|
|
|
|
|
// ProjectV2ToScreen: divide camera x/y by camera z (with chunk5's
|
|
// fixed-point shift-and-subtract divide) and bias to screen pixels.
|
|
// Returns false if the vertex is behind the camera (cz <= 0).
|
|
bool sceneryProjectVertexToScreen(const SceneryVertexT *v, int16_t *outX, int16_t *outY);
|
|
|
|
|
|
// EmitPrimaryVertex: append `slot` to the pool, AND its outcode into
|
|
// the polygon accumulator. No-op if the pool is full (chunk5 caps at
|
|
// 60 too -- $cpy #$3C / bcs).
|
|
void sceneryEmitPrimary(SceneryPipelineT *pipe, const SceneryVertexT *slot);
|
|
|
|
|
|
// 4-pass Sutherland-Hodgman 3D frustum clipper. Mirrors chunk5's
|
|
// PolygonScanFillSetup + PolygonClipTopPass + PolygonClipRightPass +
|
|
// PolygonClipBottomPass (src/chunk5.s:2884+). Operates on camera-space
|
|
// XYZ vertices, intersects each clip plane at the frustum half-spaces
|
|
// (Left: Z-X=0, Top: Z-Y=0, Right: Z+X=0, Bottom: Z+Y=0), introducing
|
|
// new vertices at the plane crossings. Ping-pongs between two arrays.
|
|
//
|
|
// On entry: `in`/`out` are arrays of capacity `cap`, `inCount` is the
|
|
// initial vertex count.
|
|
//
|
|
// Returns the final clipped vertex count, with the output in whichever
|
|
// of the two arrays the last pass wrote to (signalled via the boolean
|
|
// returned in `*outIsIn`: true means the final result is in `in`,
|
|
// false means it's in `out`).
|
|
//
|
|
// Returns 0 if the polygon was fully clipped away.
|
|
int sceneryClipPolygon3D(SceneryVertexT *in, SceneryVertexT *out, int inCount, int cap, bool *outIsIn);
|
|
|
|
|
|
#endif
|