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.
552 lines
16 KiB
552 lines
16 KiB
/*
|
|
* LibPSn00b Example Programs
|
|
*
|
|
* Hello World Example
|
|
* 2019-2020 Meido-Tek Productions / PSn00bSDK Project
|
|
*
|
|
* The obligatory hello world example normally included in nearly every
|
|
* SDK package. This example should also get you started in how to manage
|
|
* the display using psxgpu.
|
|
*
|
|
* Example by Lameguy64
|
|
*
|
|
*
|
|
* Changelog:
|
|
*
|
|
* January 1, 2020 - Initial version
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <psxetc.h>
|
|
#include <psxgte.h>
|
|
#include <psxgpu.h>
|
|
#include <inline_c.h>
|
|
|
|
#include "ps1mdl.h"
|
|
|
|
#define OTLEN 256
|
|
|
|
#define SCREENWIDTH 512
|
|
#define SCREENHEIGHT 240
|
|
|
|
#define NUMVERTEXNORMALS 162
|
|
static short anorms[NUMVERTEXNORMALS][4] = {
|
|
#include "ps1anorms.h"
|
|
};
|
|
|
|
// Define display/draw environments for double buffering
|
|
DISPENV disp[2];
|
|
DRAWENV draw[2];
|
|
int db;
|
|
|
|
u_long ot[2][OTLEN]; // Ordering tables, two arrays for double buffering. These are basically the buckets for bucket sorting of polygons.
|
|
// You can also see them as "layers" in the modern 3D graphics sense, but they serve a more immediate role for polygon ordering.
|
|
char primbuff[2][65536]; // Primitive buffer, just a raw buffer of bytes to use as a pool for primitives
|
|
char *nextpri;
|
|
|
|
extern u_long mdl_player[];
|
|
extern u_long tim_player_f[];
|
|
extern u_long tim_player_b[];
|
|
extern u_long mdl_shambler[];
|
|
extern u_long tim_shambler_f[];
|
|
extern u_long tim_shambler_b[];
|
|
|
|
typedef struct
|
|
{
|
|
ps1mdl_header_t* header;
|
|
ps1mdl_texcoord_t* texCoords;
|
|
ps1mdl_triangle_t* triangles;
|
|
ps1mdl_vertex_t* vertices;
|
|
|
|
VECTOR position;
|
|
SVECTOR rotation;
|
|
} ps1mdl_t;
|
|
|
|
typedef struct
|
|
{
|
|
RECT prect, crect;
|
|
int mode;
|
|
int uoffs, voffs;
|
|
} ps1texture_t;
|
|
|
|
typedef struct
|
|
{
|
|
// Skin textures are divided into two pieces, so that each part is aligned to their own texture page
|
|
ps1texture_t front, back;
|
|
} ps1skin_t;
|
|
|
|
ps1mdl_t playerModel = { .position = { -150, 0, 0 }, .rotation = { 0 } };
|
|
ps1mdl_t shamblerModel = { .position = { 150, 0, 0 }, .rotation = { 0, 0, ONE >> 1, 0 } };
|
|
ps1skin_t playerSkin, shamblerSkin;
|
|
|
|
const MATRIX identity = {
|
|
ONE, 0, 0,
|
|
0, ONE, 0,
|
|
0, 0, ONE,
|
|
0, 0, 0,
|
|
};
|
|
|
|
const MATRIX quake_swizzle = {
|
|
ONE, 0, 0,
|
|
0, 0, -ONE,
|
|
0, ONE, 0,
|
|
0, 0, 0,
|
|
};
|
|
|
|
MATRIX light_cols = {
|
|
ONE, 0, 0,
|
|
ONE, 0, 0,
|
|
ONE, 0, 0
|
|
};
|
|
|
|
MATRIX light_dirs = {
|
|
ONE, 0, 0,
|
|
0, 0, 0,
|
|
0, 0, 0
|
|
};
|
|
|
|
// Scale X coordinates to correct the aspect ratio for the chosen resolution
|
|
VECTOR aspect_scale = { SCREENWIDTH * ONE / 320 , ONE, ONE };
|
|
|
|
VECTOR cam_pos = { 0, 0, 0 };
|
|
SVECTOR cam_rot = { 0 };
|
|
|
|
void loadModel(const u_long* data, ps1mdl_t *mdl)
|
|
{
|
|
const char *bytes = (const char*)data;
|
|
|
|
mdl->header = (ps1mdl_header_t*)bytes;
|
|
bytes += sizeof(ps1mdl_header_t);
|
|
|
|
mdl->texCoords = (ps1mdl_texcoord_t*)bytes;
|
|
bytes += sizeof(ps1mdl_texcoord_t) * mdl->header->vertexCount;
|
|
|
|
mdl->triangles = (ps1mdl_triangle_t*)bytes;
|
|
bytes += sizeof(ps1mdl_triangle_t) * mdl->header->triangleCount;
|
|
|
|
mdl->vertices = (ps1mdl_vertex_t*)bytes;
|
|
}
|
|
|
|
void loadTexture(const u_long* tim, ps1texture_t *texture)
|
|
{
|
|
TIM_IMAGE* tparam;
|
|
GetTimInfo(tim, tparam);
|
|
|
|
// This freezes up if the TIM image has weird dimensions, such as the ones from Quake...
|
|
LoadImage(tparam->prect, tparam->paddr);
|
|
|
|
// Upload CLUT for palettized images
|
|
if (tparam->mode & 0x8)
|
|
{
|
|
LoadImage(tparam->crect, tparam->caddr);
|
|
}
|
|
|
|
DrawSync(0);
|
|
|
|
texture->prect = *tparam->prect;
|
|
texture->crect = *tparam->crect;
|
|
texture->mode = tparam->mode;
|
|
|
|
texture->uoffs = (texture->prect.x % 64) << (2 - (texture->mode & 0x3));
|
|
texture->voffs = (texture->prect.y & 0xFF);
|
|
}
|
|
|
|
void loadSkin(const u_long* frontTim, const u_long* backTim, ps1skin_t *skin)
|
|
{
|
|
loadTexture(frontTim, &skin->front);
|
|
loadTexture(backTim, &skin->back);
|
|
}
|
|
|
|
// Init function
|
|
void init(void)
|
|
{
|
|
// This not only resets the GPU but it also installs the library's
|
|
// ISR subsystem to the kernel
|
|
ResetGraph(0);
|
|
|
|
SetVideoMode(MODE_NTSC);
|
|
|
|
// Define display environments, first on top and second on bottom
|
|
SetDefDispEnv(&disp[0], 0, 0, SCREENWIDTH, SCREENHEIGHT);
|
|
SetDefDispEnv(&disp[1], 0, SCREENHEIGHT, SCREENWIDTH, SCREENHEIGHT);
|
|
|
|
// Define drawing environments, first on bottom and second on top
|
|
SetDefDrawEnv(&draw[0], 0, SCREENHEIGHT, SCREENWIDTH, SCREENHEIGHT);
|
|
SetDefDrawEnv(&draw[1], 0, 0, SCREENWIDTH, SCREENHEIGHT);
|
|
|
|
// Set and enable clear color
|
|
setRGB0(&draw[0], 49, 77, 121);
|
|
setRGB0(&draw[1], 49, 77, 121);
|
|
draw[0].isbg = 1;
|
|
draw[0].dtd = 1;
|
|
draw[1].isbg = 1;
|
|
draw[1].dtd = 1;
|
|
|
|
// Clear double buffer counter
|
|
db = 0;
|
|
|
|
// Apply the GPU environments
|
|
PutDispEnv(&disp[db]);
|
|
PutDrawEnv(&draw[db]);
|
|
|
|
// Load test font
|
|
FntLoad(960, 0);
|
|
|
|
// Open up a test font text stream of 100 characters
|
|
FntOpen(0, 8, 320, 224, 0, 100);
|
|
|
|
nextpri = primbuff[0];
|
|
|
|
loadModel(mdl_player, &playerModel);
|
|
loadSkin(tim_player_f, tim_player_b, &playerSkin);
|
|
|
|
loadModel(mdl_shambler, &shamblerModel);
|
|
loadSkin(tim_shambler_f, tim_shambler_b, &shamblerSkin);
|
|
|
|
InitGeom();
|
|
gte_SetGeomOffset(SCREENWIDTH >> 1, SCREENHEIGHT >> 1);
|
|
gte_SetGeomScreen(180); // Screen depth for FOV control. Essentially the Z position of the vanishing point.
|
|
|
|
// Set texture page for the entire drawing environment. Nice in some cases perhaps, but not what we need.
|
|
//draw[0].tpage = getTPage(playerFrontTex.mode & 0x3, 0, playerFrontTex.prect->x, playerFrontTex.prect->y);
|
|
//draw[1].tpage = getTPage(playerFrontTex.mode & 0x3, 0, playerFrontTex.prect->x, playerFrontTex.prect->y);
|
|
}
|
|
|
|
// Display function
|
|
void display(void)
|
|
{
|
|
// Wait for all drawing to complete
|
|
// TODO: this should be interleaved with the double buffered ordering table draw commands, to minimize CPU idle time while waiting for the GPU
|
|
DrawSync(0);
|
|
|
|
// Wait for vertical sync to cap the logic to 60fps (or 50 in PAL mode)
|
|
// and prevent screen tearing
|
|
VSync(0);
|
|
|
|
// Switch pages
|
|
PutDispEnv(&disp[db]);
|
|
PutDrawEnv(&draw[db]);
|
|
|
|
// Enable display output, ResetGraph() disables it by default
|
|
SetDispMask(1);
|
|
|
|
DrawOTag(ot[db] + OTLEN - 1); // This performs a DMA transfer to quickly send all the primitives off to the GPU
|
|
|
|
// Flip buffer index
|
|
db = !db;
|
|
|
|
nextpri = primbuff[db];
|
|
}
|
|
|
|
static void lerpcol(int r0, int g0, int b0, int r1, int g1, int b1, int lerp, int* rout, int* gout, int* bout) // lerp = 0-4096
|
|
{
|
|
int invlerp = 4096 - lerp;
|
|
|
|
*rout = (r0 * invlerp + r1 * lerp) >> 12;
|
|
*gout = (g0 * invlerp + g1 * lerp) >> 12;
|
|
*bout = (b0 * invlerp + b1 * lerp) >> 12;
|
|
}
|
|
|
|
void addTile(int x, int y, int w, int h, int r, int g, int b)
|
|
{
|
|
// Initialize tile primitive
|
|
TILE *tile = (TILE*)nextpri;
|
|
setTile(tile);
|
|
setXY0(tile, x, y);
|
|
setWH(tile, w, h);
|
|
setRGB0(tile, r, g, b);
|
|
|
|
addPrim(ot[db], tile);
|
|
nextpri += sizeof(TILE);
|
|
}
|
|
|
|
void addColoredTriangle(int x0, int y0, int x1, int y1, int x2, int y2, unsigned int rgb0, unsigned int rgb1, unsigned int rgb2, unsigned char depth)
|
|
{
|
|
POLY_G3 *poly = (POLY_G3*)nextpri;
|
|
setPolyG3(poly);
|
|
setXY3(poly, x0, y0, x1, y1, x2, y2);
|
|
setRGB0(poly, (rgb0 >> 16) & 0xFF, (rgb0 >> 8) & 0xFF, rgb0 & 0xFF);
|
|
setRGB1(poly, (rgb1 >> 16) & 0xFF, (rgb1 >> 8) & 0xFF, rgb1 & 0xFF);
|
|
setRGB2(poly, (rgb2 >> 16) & 0xFF, (rgb2 >> 8) & 0xFF, rgb2 & 0xFF);
|
|
|
|
addPrim(ot[db] + depth, poly);
|
|
nextpri += sizeof(POLY_G3);
|
|
}
|
|
|
|
void addTexturedTriangle(int x0, int y0, int x1, int y1, int x2, int y2, short u0, short v0, short u1, short v1, short u2, short v2, TIM_IMAGE *img)
|
|
{
|
|
short uoffs = (img->prect->x % 64) << (2 - (img->mode & 0x3));
|
|
short voffs = (img->prect->y & 0xFF);
|
|
|
|
POLY_FT3 *poly = (POLY_FT3*)nextpri;
|
|
setPolyFT3(poly);
|
|
setXY3(poly, x0, y0, x1, y1, x2, y2);
|
|
setUV3(poly, uoffs + u0, voffs + v0, uoffs + u1, voffs + v1, uoffs + u2, voffs + v2);
|
|
setRGB0(poly, 128, 128, 128);
|
|
setClut(poly, img->crect->x, img->crect->y);
|
|
setTPage(poly, img->mode & 0x3, 0, img->prect->x, img->prect->y);
|
|
|
|
addPrim(ot[db], poly);
|
|
nextpri += sizeof(POLY_FT3);
|
|
}
|
|
|
|
void addGouraudTexturedTriangle(
|
|
int x0, int y0, int x1, int y1, int x2, int y2, unsigned char depth,
|
|
unsigned int rgb0, unsigned int rgb1, unsigned int rgb2,
|
|
short u0, short v0, short u1, short v1, short u2, short v2, TIM_IMAGE *img)
|
|
{
|
|
if (img == NULL)
|
|
{
|
|
addColoredTriangle(x0, y0, x1, y1, x2, y2, rgb0, rgb1, rgb2, depth);
|
|
return;
|
|
}
|
|
|
|
short uoffs = (img->prect->x % 64) << (2 - (img->mode & 0x3));
|
|
short voffs = (img->prect->y & 0xFF);
|
|
|
|
POLY_GT3 *poly = (POLY_GT3*)nextpri;
|
|
setPolyGT3(poly);
|
|
setXY3(poly, x0, y0, x1, y1, x2, y2);
|
|
setUV3(poly, uoffs + u0, voffs + v0, uoffs + u1, voffs + v1, uoffs + u2, voffs + v2);
|
|
setRGB0(poly, (rgb0 >> 16) & 0xFF, (rgb0 >> 8) & 0xFF, rgb0 & 0xFF);
|
|
setRGB1(poly, (rgb1 >> 16) & 0xFF, (rgb1 >> 8) & 0xFF, rgb1 & 0xFF);
|
|
setRGB2(poly, (rgb2 >> 16) & 0xFF, (rgb2 >> 8) & 0xFF, rgb2 & 0xFF);
|
|
setClut(poly, img->crect->x, img->crect->y);
|
|
setTPage(poly, img->mode & 0x3, 0, img->prect->x, img->prect->y);
|
|
|
|
addPrim(ot[db] + depth, poly);
|
|
nextpri += sizeof(POLY_GT3);
|
|
}
|
|
|
|
void addTexturedSprite(int x, int y, int w, int h, TIM_IMAGE *img)
|
|
{
|
|
short uoffs = (img->prect->x % 64) << (2 - (img->mode & 0x3));
|
|
short voffs = (img->prect->y & 0xFF);
|
|
|
|
SPRT *sprite = (SPRT*)nextpri;
|
|
setSprt(sprite);
|
|
setXY0(sprite, x, y);
|
|
setWH(sprite, w, h);
|
|
setUV0(sprite, uoffs, voffs);
|
|
setClut(sprite, img->crect->x, img->crect->y);
|
|
setRGB0(sprite, 128, 128, 128);
|
|
addPrim(ot[db], sprite);
|
|
nextpri += sizeof(SPRT);
|
|
|
|
DR_TPAGE *tpage = (DR_TPAGE*)nextpri;
|
|
setDrawTPage(tpage, 0, 1, getTPage(img->mode & 0x3, 0, img->prect->x, img->prect->y));
|
|
addPrim(ot[db], tpage);
|
|
nextpri += sizeof(DR_TPAGE);
|
|
}
|
|
|
|
static int normToRGB(const int *norm)
|
|
{
|
|
int r = ((norm[0] + 4096) >> 5) & 0xFF;
|
|
int g = ((norm[1] + 4096) >> 5) & 0xFF;
|
|
int b = ((norm[2] + 4096) >> 5) & 0xFF;
|
|
return r << 16 | g << 8 | b;
|
|
}
|
|
|
|
static int dot(const int *a, const int *b)
|
|
{
|
|
int c0 = (a[0] * b[0]) >> 12;
|
|
int c1 = (a[1] * b[1]) >> 12;
|
|
int c2 = (a[2] * b[2]) >> 12;
|
|
return c0 + c1 + c2;
|
|
}
|
|
|
|
static int fakeLight(const int *norm)
|
|
{
|
|
const int light[3] = { ONE, 0, 0 };
|
|
int lit = dot(norm, light) >> 4; // Calculate light intensity and normalize to [-256..255]
|
|
lit = lit < 0 ? 0 : lit; // Clamp to [0..255]
|
|
lit += 48; // Add some ambient light
|
|
lit = lit > 255 ? 255 : lit; // Clamp to [0..255] again
|
|
return lit;
|
|
}
|
|
|
|
SVECTOR outPos;
|
|
|
|
#define MAXVERTEXBUF 512
|
|
SVECTOR vertexBuffer[MAXVERTEXBUF] = { 0 }; // Temporary buffer for precalculating vertex data
|
|
|
|
#define SCTR(v, i) (short)((scale[i]*(v)->position[i]+translate[i])>>10)
|
|
|
|
void drawModel(MATRIX *view_matrix, ps1mdl_t *model, ps1skin_t *skin, int frameCounter)
|
|
{
|
|
ps1texture_t *tex;
|
|
short u0, u1, u2, uoffs, voffs;
|
|
int p;
|
|
|
|
short halfSkinWidth = model->header->skinWidth >> 1;
|
|
unsigned short vertexCount = model->header->vertexCount;
|
|
unsigned short triangleCount = model->header->triangleCount;
|
|
int frameNum = frameCounter % model->header->frameCount;
|
|
ps1mdl_vertex_t *baseVertex = &model->vertices[frameNum * vertexCount];
|
|
|
|
int *scale = model->header->scale;
|
|
int *translate = model->header->translate;
|
|
|
|
// Model world position and rotation
|
|
MATRIX model_mtx;
|
|
RotMatrix(&model->rotation, &model_mtx);
|
|
TransMatrix(&model_mtx, &model->position);
|
|
|
|
// Adjust light direction by the model's rotation relative to the world
|
|
MATRIX light_mtx;
|
|
MulMatrix0(&light_dirs, &model_mtx, &light_mtx);
|
|
gte_SetLightMatrix(&light_mtx);
|
|
|
|
// Compose model matrix with view-projection matrix to obtain the final model-view-projection matrix
|
|
CompMatrixLV(view_matrix, &model_mtx, &model_mtx);
|
|
|
|
// TODO: this idea is good but it's missing the >> 12 fraction divide after transformation. Hmmmm...
|
|
// MATRIX mesh_mtx = identity;
|
|
// ScaleMatrixL(&mesh_mtx, (VECTOR*)scale);
|
|
// TransMatrix(&mesh_mtx, (VECTOR*)translate);
|
|
// CompMatrixLV(&model_mtx, &mesh_mtx, &mesh_mtx);
|
|
|
|
gte_SetRotMatrix(&model_mtx);
|
|
gte_SetTransMatrix(&model_mtx);
|
|
|
|
ps1mdl_vertex_t *vin = baseVertex;
|
|
for (unsigned short vertIdx = 0; vertIdx < vertexCount; ++vertIdx, ++vin)
|
|
{
|
|
SVECTOR *v = &vertexBuffer[vertIdx];
|
|
setVector(v, SCTR(vin,0), SCTR(vin,1), SCTR(vin,2));
|
|
}
|
|
|
|
for (unsigned short triIdx = 0; triIdx < triangleCount; ++triIdx)
|
|
{
|
|
ps1mdl_triangle_t *tri = &model->triangles[triIdx];
|
|
|
|
ps1mdl_vertex_t *v0 = &baseVertex[tri->vertexIndex[0]];
|
|
ps1mdl_vertex_t *v1 = &baseVertex[tri->vertexIndex[1]];
|
|
ps1mdl_vertex_t *v2 = &baseVertex[tri->vertexIndex[2]];
|
|
|
|
SVECTOR *pos0 = &vertexBuffer[tri->vertexIndex[0]];
|
|
SVECTOR *pos1 = &vertexBuffer[tri->vertexIndex[1]];
|
|
SVECTOR *pos2 = &vertexBuffer[tri->vertexIndex[2]];
|
|
|
|
gte_ldv3(pos0, pos1, pos2);
|
|
gte_rtpt(); // Rotation, Translation and Perspective triplet (all three vertices at once)
|
|
|
|
// Normal clipping for backface culling
|
|
gte_nclip();
|
|
gte_stopz(&p);
|
|
if (p < 0)
|
|
continue;
|
|
|
|
// Average Z for depth sorting
|
|
gte_avsz3();
|
|
gte_stotz(&p);
|
|
unsigned short depth = p;//p >> 2;
|
|
if (depth < 0 || depth >= OTLEN)
|
|
continue;
|
|
|
|
ps1mdl_texcoord_t *tc0 = &model->texCoords[tri->vertexIndex[0]];
|
|
ps1mdl_texcoord_t *tc1 = &model->texCoords[tri->vertexIndex[1]];
|
|
ps1mdl_texcoord_t *tc2 = &model->texCoords[tri->vertexIndex[2]];
|
|
|
|
if (tri->frontFace)
|
|
{
|
|
u0 = tc0->u, u1 = tc1->u, u2 = tc2->u;
|
|
tex = &skin->front;
|
|
}
|
|
else
|
|
{
|
|
// Since we have the texture split into two parts, we need to correct the UVs that are *not* on the seam, instead of the other way round
|
|
u0 = tc0->u - !tc0->onSeam * halfSkinWidth;
|
|
u1 = tc1->u - !tc1->onSeam * halfSkinWidth;
|
|
u2 = tc2->u - !tc2->onSeam * halfSkinWidth;
|
|
tex = &skin->back;
|
|
}
|
|
|
|
// TODO: we could get rid of these silly offsets if we just ensure that textures are always page-aligned
|
|
uoffs = tex->uoffs, voffs = tex->voffs;
|
|
|
|
POLY_GT3 *poly = (POLY_GT3*)nextpri;
|
|
setPolyGT3(poly);
|
|
|
|
// Store transformed vertex coordinates in screen space
|
|
gte_stsxy3_gt3(poly);
|
|
|
|
// Copy some values for on-screen debugging
|
|
outPos.vx = poly->x0;
|
|
outPos.vy = poly->y0;
|
|
outPos.vz = p;
|
|
|
|
// Calculate vertex color based on normal
|
|
// TODO: we could probably speed this up by precalculating the lighting for each normal, if we need to draw more than 162 vertices
|
|
gte_ldrgb(&poly->r0);
|
|
gte_ldv3(anorms[v0->normalIndex], anorms[v1->normalIndex], anorms[v2->normalIndex]);
|
|
gte_nct();
|
|
gte_strgb3(&poly->r0, &poly->r1, &poly->r2);
|
|
|
|
// Set texture parameters
|
|
setUV3(poly, uoffs + u0, voffs + tc0->v, uoffs + u1, voffs + tc1->v, uoffs + u2, voffs + tc2->v);
|
|
setClut(poly, tex->crect.x, tex->crect.y);
|
|
setTPage(poly, tex->mode & 0x3, 0, tex->prect.x, tex->prect.y);
|
|
|
|
addPrim(ot[db] + depth, poly);
|
|
nextpri += sizeof(POLY_GT3);
|
|
}
|
|
}
|
|
|
|
void drawStuff(int counter)
|
|
{
|
|
ClearOTagR(ot[db], OTLEN);
|
|
|
|
FntPrint(-1, "Image x %d y %d w %d h %d mode 0x%x\n", playerSkin.front.prect.x, playerSkin.front.prect.y, playerSkin.front.prect.w, playerSkin.front.prect.h, playerSkin.front.mode);
|
|
FntPrint(-1, "Model: %d tris, %d verts\n", playerModel.header->triangleCount, playerModel.header->vertexCount);
|
|
FntPrint(-1, "Trsf: (%d, %d, %d)\n", outPos.vx, outPos.vy, outPos.vz);
|
|
|
|
// Draw the last created text stream
|
|
FntFlush(-1);
|
|
|
|
gte_SetBackColor(48, 48, 48); // Ambient light color
|
|
gte_SetColorMatrix(&light_cols); // Light color (up to three different lights)
|
|
|
|
MATRIX proj_matrix = quake_swizzle; // Swizzle coordinates so that everything is in Quake coordinate system (Z up)
|
|
ScaleMatrixL(&proj_matrix, &aspect_scale); // Apply aspect ratio correction for the current resolution
|
|
|
|
MATRIX view_matrix;
|
|
SVECTOR trot = { 0, 0, 0, 0 }; // TODO: inverse camera rotation (in Quake coordinates)
|
|
VECTOR tpos = { 0, 400, -100 }; // TODO: inverse camera position (in Quake coordinates)
|
|
RotMatrix(&trot, &view_matrix); // Set camera rotation part of the view matrix
|
|
ApplyMatrixLV(&view_matrix, &tpos, &tpos); // Apply camera rotation to camera position
|
|
TransMatrix(&view_matrix, &tpos); // Apply transformed position to the translation part of the view matrix
|
|
|
|
// Compose view and projection matrices to obtain a combined view-projection matrix
|
|
CompMatrixLV(&proj_matrix, &view_matrix, &view_matrix);
|
|
|
|
drawModel(&view_matrix, &playerModel, &playerSkin, counter >> 2);
|
|
drawModel(&view_matrix, &shamblerModel, &shamblerSkin, counter >> 2);
|
|
}
|
|
|
|
// Main function, program entrypoint
|
|
int main(int argc, const char *argv[])
|
|
{
|
|
int counter;
|
|
|
|
// Init stuff
|
|
init();
|
|
|
|
// Main loop
|
|
counter = 0;
|
|
while(1)
|
|
{
|
|
//playerModel.rotation.vz = (counter << 2) % ONE;
|
|
|
|
drawStuff(counter);
|
|
|
|
// Update display
|
|
display();
|
|
|
|
// Increment the counter
|
|
counter++;
|
|
}
|
|
|
|
return 0;
|
|
}
|