/* * LibPSn00b Example Programs * * Full-resolution, Anamorphic Widescreen 3D Example * 2020 - 2021 Meido-Tek Productions / PSn00bSDK Project * * This example is a modification of the fpscam example incorporating * techniques for taking advantage of wide-screen televisions by means * of a custom 704x480 video mode (as opposed to 640x480) and applying * anamorphic aspect correction to a 3D perspective matrix. * * The extended horizontal resolution guarantees that the video display * will fill a wide-screen television in 16:9 mode fully, whereas the more * conventional 640x480 mode displays black borders on the sides of the * picture on most wide-screen television sets- notably LCD and Plasma type * television sets. * * However, this extended video mode may cause display problems on some * analog television sets- such as conventional ratio Sony Trinitron * televisions if the video display is moved too close to the left of the * screen. Therefore it is recommended to support this extended video mode * as an option to ensure compatibility with affected televisions. * * Use of the extended video mode does not provide aspect correct pixels * for widescreen displays (the extended mode merely displays more columns * of pixels to fill the side borders of widescreen displays), thus 2D * bitmap graphics will appear stretched as it would when using a more * conventional 640x480 display resolution on a wide-screen ratio * television. * * If lower resolution 240/256 line mode is desired whilst being presentable * on a wide-screen television display, use 384x240 or 384x256 as those * display resolutions can not only cover the side borders of a wide-screen * television display but it also provides close to aspect correct pixels * on such television sets and is well suited for 2D games. The anamorphic * aspect correction demonstrated in this example can also work on the more * conventional 320x240/256 modes for 3D games when such resolutions are more * desired. * * When 240/256 line video modes are used, it is highly recommended to * consider implementing an option to enable interlace even if the software * title does not run in high resolution mode. This is to provide * compatibility with digital EDTV or HDTV televisions as some of these * televisions have issues accepting non-interlaced video signals. On some * televisions enabling interlace can improve the picture quality. * * Controls: * Up - Look up * Down - Look down * Left - Look left * Right - Look right * Triangle - Move forward * Cross - Move backward * Square - Strafe left * Circle - Strafe right * R1 - Slide up * R2 - Slide down * L1 - Look at cube (tracking) * Select - Exit program (only works with CD loaders) * * * Example by Lameguy64 * * Changelog: * * November 23, 2021 - Improved example description. * * May 10, 2021 - Variable types updated for psxgpu.h changes. * * November 27, 2020 - Initial version. * */ #include #include #include #include #include #include #include #include #include "clip.h" #include "lookat.h" // OT and Packet Buffer sizes #define OT_LEN 1024 #define PACKET_LEN 8192 // Screen resolution #define SCREEN_XRES 704 #define SCREEN_YRES 480 // Screen center position #define CENTERX SCREEN_XRES>>1 #define CENTERY SCREEN_YRES>>1 // Double buffer structure typedef struct { DISPENV disp; // Display environment DRAWENV draw; // Drawing environment u_long ot[OT_LEN]; // Ordering table char p[PACKET_LEN]; // Packet buffer } DB; // Double buffer variables DB db[2]; int db_active = 0; char *db_nextpri; RECT screen_clip; // Pad data buffer char pad_buff[2][34]; // For easier handling of vertex indexes typedef struct { short v0,v1,v2,v3; } INDEX; // Cube vertices SVECTOR cube_verts[] = { { -100, -100, -100, 0 }, { 100, -100, -100, 0 }, { -100, 100, -100, 0 }, { 100, 100, -100, 0 }, { 100, -100, 100, 0 }, { -100, -100, 100, 0 }, { 100, 100, 100, 0 }, { -100, 100, 100, 0 } }; // Cube face normals SVECTOR cube_norms[] = { { 0, 0, -ONE, 0 }, { 0, 0, ONE, 0 }, { 0, -ONE, 0, 0 }, { 0, ONE, 0, 0 }, { -ONE, 0, 0, 0 }, { ONE, 0, 0, 0 } }; // Cube vertex indices INDEX cube_indices[] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 5, 4, 0, 1 }, { 6, 7, 3, 2 }, { 0, 2, 5, 7 }, { 3, 1, 6, 4 } }; // Number of faces of cube #define CUBE_FACES 6 // Light color matrix // Each column represents the color matrix of each light source and is // used as material color when using gte_ncs() or multiplied by a // source color when using gte_nccs(). 4096 is 1.0 in this matrix // A column of zeroes effectively disables the light source. MATRIX color_mtx = { ONE, 0, 0, // Red 0, 0, 0, // Green ONE, 0, 0 // Blue }; // Light matrix // Each row represents a vector direction of each light source. // An entire row of zeroes effectively disables the light source. MATRIX light_mtx = { /* X, Y, Z */ -2048 , -2048 , -2048, 0 , 0 , 0, 0 , 0 , 0 }; // Function declarations void init(); void display(); void sort_cube(MATRIX *mtx, VECTOR *pos, SVECTOR *rot); // Main function int main() { int i,p,xy_temp; int px,py; SVECTOR rot; // Rotation vector for cube VECTOR pos; // Position vector for cube SVECTOR verts[17][17]; // Vertex array for floor VECTOR cam_pos; // Camera position (in fixed point integers) VECTOR cam_rot; // Camera view angle (in fixed point integers) int cam_mode; // Camera mode (between first-person and look-at) VECTOR cam_scale; VECTOR tpos; // Translation value for matrix calculations SVECTOR trot; // Rotation value for matrix calculations MATRIX mtx,lmtx; // Rotation matrices for geometry and lighting PADTYPE *pad; // Pad structure pointer for parsing controller POLY_F4 *pol4; // Flat shaded quad primitive pointer // Init graphics and GTE init(); // Set coordinates to the vertex array for the floor for( py=0; py<17; py++ ) { for( px=0; px<17; px++ ) { setVector( &verts[py][px], (100*(px-8))-50, 0, (100*(py-8))-50 ); } } // Camera default coordinates setVector( &cam_pos, 0, ONE*-200, 0 ); setVector( &cam_rot, 0, 0, 0 ); setVector( &cam_scale, 3008, ONE, ONE ); // Main loop while( 1 ) { // Set pad buffer data to pad pointer pad = (PADTYPE*)&pad_buff[0][0]; // Parse controller input cam_mode = 0; // Divide out fractions of camera rotation trot.vx = cam_rot.vx >> 12; trot.vy = cam_rot.vy >> 12; trot.vz = cam_rot.vz >> 12; if( pad->stat == 0 ) { // For digital pad, dual-analog and dual-shock if( ( pad->type == 0x4 ) || ( pad->type == 0x5 ) || ( pad->type == 0x7 ) ) { // The button status bits are inverted, // so 0 means pressed in this case // Look controls if( !(pad->btn&PAD_UP) ) { // Look up cam_rot.vx -= ONE*8; } else if( !(pad->btn&PAD_DOWN) ) { // Look down cam_rot.vx += ONE*8; } if( !(pad->btn&PAD_LEFT) ) { // Look left cam_rot.vy += ONE*8; } else if( !(pad->btn&PAD_RIGHT) ) { // Look right cam_rot.vy -= ONE*8; } // Movement controls if( !(pad->btn&PAD_TRIANGLE) ) { // Move forward cam_pos.vx -= (( isin( trot.vy )*icos( trot.vx ) )>>12)<<2; cam_pos.vy += isin( trot.vx )<<2; cam_pos.vz += (( icos( trot.vy )*icos( trot.vx ) )>>12)<<2; } else if( !(pad->btn&PAD_CROSS) ) { // Move backward cam_pos.vx += (( isin( trot.vy )*icos( trot.vx ) )>>12)<<2; cam_pos.vy -= isin( trot.vx )<<2; cam_pos.vz -= (( icos( trot.vy )*icos( trot.vx ) )>>12)<<2; } if( !(pad->btn&PAD_SQUARE ) ) { // Slide left cam_pos.vx -= icos( trot.vy )<<2; cam_pos.vz -= isin( trot.vy )<<2; } else if( !(pad->btn&PAD_CIRCLE ) ) { // Slide right cam_pos.vx += icos( trot.vy )<<2; cam_pos.vz += isin( trot.vy )<<2; } if( !(pad->btn&PAD_R1) ) { // Slide up cam_pos.vx -= (( isin( trot.vy )*isin( trot.vx ) )>>12)<<2; cam_pos.vy -= icos( trot.vx )<<2; cam_pos.vz += (( icos( trot.vy )*isin( trot.vx ) )>>12)<<2; } if( !(pad->btn&PAD_R2) ) { // Slide down cam_pos.vx += (( isin( trot.vy )*isin( trot.vx ) )>>12)<<2; cam_pos.vy += icos( trot.vx )<<2; cam_pos.vz -= (( icos( trot.vy )*isin( trot.vx ) )>>12)<<2; } // Look at cube if( !(pad->btn&PAD_L1) ) { cam_mode = 1; } if( !(pad->btn&PAD_SELECT) ) { _boot(); } } // For dual-analog and dual-shock (analog input) if( ( pad->type == 0x5 ) || ( pad->type == 0x7 ) ) { // Moving forwards and backwards if( ( (pad->ls_y-128) < -16 ) || ( (pad->ls_y-128) > 16 ) ) { cam_pos.vx += ((( isin( trot.vy )*icos( trot.vx ) )>>12)*(pad->ls_y-128))>>5; cam_pos.vy -= (isin( trot.vx )*(pad->ls_y-128))>>5; cam_pos.vz -= ((( icos( trot.vy )*icos( trot.vx ) )>>12)*(pad->ls_y-128))>>5; } // Strafing left and right if( ( (pad->ls_x-128) < -16 ) || ( (pad->ls_x-128) > 16 ) ) { cam_pos.vx += (icos( trot.vy )*(pad->ls_x-128))>>5; cam_pos.vz += (isin( trot.vy )*(pad->ls_x-128))>>5; } // Look up and down if( ( (pad->rs_y-128) < -16 ) || ( (pad->rs_y-128) > 16 ) ) { cam_rot.vx += (pad->rs_y-128)<<9; } // Look left and right if( ( (pad->rs_x-128) < -16 ) || ( (pad->rs_x-128) > 16 ) ) { cam_rot.vy -= (pad->rs_x-128)<<9; } } } // Print out some info FntPrint(-1, "BUTTONS=%04x\n", pad->btn); FntPrint(-1, "X=%d Y=%d Z=%d\n", cam_pos.vx>>12, cam_pos.vy>>12, cam_pos.vz>>12); FntPrint(-1, "RX=%d RY=%d\n", cam_rot.vx>>12, cam_rot.vy>>12); if( cam_mode == 0 ) { // First-person camera mode // Set rotation to the matrix RotMatrix( &trot, &mtx ); // Divide out the fractions of camera coordinates and invert // the sign, so camera coordinates will line up to world // (or geometry) coordinates tpos.vx = -cam_pos.vx >> 12; tpos.vy = -cam_pos.vy >> 12; tpos.vz = -cam_pos.vz >> 12; // Squish perspective horizontally for 16:9 aspect correction ScaleMatrixL(&mtx, &cam_scale); // Apply rotation of matrix to translation value to achieve a // first person perspective ApplyMatrixLV( &mtx, &tpos, &tpos ); // Set translation to matrix TransMatrix( &mtx, &tpos ); } else { // Tracking mode // Vector that defines the 'up' direction of the camera SVECTOR up = { 0, -ONE, 0 }; // Divide out fractions of camera coordinates tpos.vx = cam_pos.vx >> 12; tpos.vy = cam_pos.vy >> 12; tpos.vz = cam_pos.vz >> 12; // Look at the cube LookAt(&tpos, &pos, &up, &mtx); // Squish perspective horizontally for 16:9 aspect correction ScaleMatrixL(&mtx, &cam_scale); // Clear translation coordinates of matrix, they aren't scaled // by ScaleMatrixL(), so the result will look wrong after scaling mtx.t[0] = 0; mtx.t[1] = 0; mtx.t[2] = 0; tpos.vx = -cam_pos.vx >> 12; tpos.vy = -cam_pos.vy >> 12; tpos.vz = -cam_pos.vz >> 12; ApplyMatrixLV( &mtx, &tpos, &tpos ); // Set translation to matrix TransMatrix( &mtx, &tpos ); } // Set rotation and translation matrix gte_SetRotMatrix( &mtx ); gte_SetTransMatrix( &mtx ); // Draw the floor pol4 = (POLY_F4*)db_nextpri; for( py=0; py<16; py++ ) { for( px=0; px<16; px++ ) { // Load first three vertices to GTE gte_ldv3( &verts[py][px], &verts[py][px+1], &verts[py+1][px] ); gte_rtpt(); gte_avsz3(); gte_stotz( &p ); if( ( (p>>2) >= OT_LEN ) || ( (p>>2) <= 0 ) ) continue; setPolyF4( pol4 ); // Set the projected vertices to the primitive gte_stsxy0( &pol4->x0 ); gte_stsxy1( &pol4->x1 ); gte_stsxy2( &pol4->x2 ); // Compute the last vertex and set the result gte_ldv0( &verts[py+1][px+1] ); gte_rtps(); gte_stsxy( &pol4->x3 ); // Test if quad is off-screen, discard if so // Clipping is important as it not only prevents primitive // overflows (tends to happen on textured polys) but also // saves packet buffer space and speeds up rendering. if( quad_clip( &screen_clip, (DVECTOR*)&pol4->x0, (DVECTOR*)&pol4->x1, (DVECTOR*)&pol4->x2, (DVECTOR*)&pol4->x3 ) ) continue; gte_avsz4(); gte_stotz( &p ); if((px+py)&0x1) { setRGB0( pol4, 128, 128, 128 ); } else { setRGB0( pol4, 255, 255, 255 ); } addPrim( db[db_active].ot+(p>>2), pol4 ); pol4++; } } // Update nextpri variable (very important) db_nextpri = (char*)pol4; // Position the cube going around the floor bouncily setVector( &pos, isin( rot.vy )>>4, -300+(isin( rot.vy<<2 )>>5), icos( rot.vy )>>3 ); // Sort cube sort_cube( &mtx, &pos, &rot ); // Make the cube SPEEN rot.vx += 8; rot.vy += 8; // Flush text to drawing area FntFlush(-1); // Swap buffers and draw the primitives display(); } return 0; } void sort_cube(MATRIX *mtx, VECTOR *pos, SVECTOR *rot) { int i,p; POLY_F4 *pol4; // Object and light matrix for object MATRIX omtx,lmtx; // Set object rotation and position RotMatrix( rot, &omtx ); TransMatrix( &omtx, pos ); // Multiply light matrix to object matrix MulMatrix0( &light_mtx, &omtx, &lmtx ); // Set result to GTE light matrix gte_SetLightMatrix( &lmtx ); // Composite coordinate matrix transform, so object will be rotated and // positioned relative to camera matrix (mtx), so it'll appear as // world-space relative. CompMatrixLV( mtx, &omtx, &omtx ); // Save matrix PushMatrix(); // Set matrices gte_SetRotMatrix( &omtx ); gte_SetTransMatrix( &omtx ); // Sort the cube pol4 = (POLY_F4*)db_nextpri; for( i=0; i>2) <= 0) || ((p>>2) >= OT_LEN) ) continue; // Initialize a quad primitive setPolyF4( pol4 ); // Set the projected vertices to the primitive gte_stsxy0( &pol4->x0 ); gte_stsxy1( &pol4->x1 ); gte_stsxy2( &pol4->x2 ); // Compute the last vertex and set the result gte_ldv0( &cube_verts[cube_indices[i].v3] ); gte_rtps(); gte_stsxy( &pol4->x3 ); // Test if quad is off-screen, discard if so if( quad_clip( &screen_clip, (DVECTOR*)&pol4->x0, (DVECTOR*)&pol4->x1, (DVECTOR*)&pol4->x2, (DVECTOR*)&pol4->x3 ) ) continue; // Load primitive color even though gte_ncs() doesn't use it. // This is so the GTE will output a color result with the // correct primitive code. gte_ldrgb( &pol4->r0 ); // Load the face normal gte_ldv0( &cube_norms[i] ); // Normal Color Single gte_ncs(); // Store result to the primitive gte_strgb( &pol4->r0 ); gte_avsz4(); gte_stotz( &p ); // Sort primitive to the ordering table addPrim( db[db_active].ot+(p>>2), pol4 ); // Advance to make another primitive pol4++; } // Update nextpri db_nextpri = (char*)pol4; // Restore matrix PopMatrix(); } void init() { // Reset the GPU, also installs a VSync event handler ResetGraph( 0 ); // Set display and draw environment areas // (display and draw areas must be separate, otherwise hello flicker) SetDefDispEnv( &db[0].disp, 0, 0, 640, SCREEN_YRES ); SetDefDrawEnv( &db[0].draw, 0, 0, SCREEN_XRES, SCREEN_YRES ); // Enable interlace (mandatory for hi-res modes) db[0].disp.isinter = 1; // Increase the picture width to 704 pixels // (without stretching it, just tell the GPU to output more pixels) db[0].disp.screen.w = (704 * 256) / 640; // Enable draw area clear and dither processing setRGB0( &db[0].draw, 63, 0, 127 ); db[0].draw.isbg = 1; db[0].draw.dtd = 1; // Define the second set of display/draw environments SetDefDispEnv( &db[1].disp, 0, 0, 640, SCREEN_YRES ); SetDefDrawEnv( &db[1].draw, 0, 0, SCREEN_XRES, SCREEN_YRES ); db[1].disp.isinter = 1; // enable interlace db[1].disp.screen = db[0].disp.screen; // copy the custom screen coords setRGB0( &db[1].draw, 63, 0, 127 ); db[1].draw.isbg = 1; db[1].draw.dtd = 1; // Apply the drawing environment of the first double buffer PutDrawEnv( &db[0].draw ); // Clear both ordering tables to make sure they are clean at the start ClearOTagR( db[0].ot, OT_LEN ); ClearOTagR( db[1].ot, OT_LEN ); // Set primitive pointer address db_nextpri = db[0].p; // Set clip region setRECT( &screen_clip, 0, 0, SCREEN_XRES, SCREEN_YRES ); // Initialize the GTE InitGeom(); // Set GTE offset (recommended method of centering) gte_SetGeomOffset( CENTERX, CENTERY ); // Set screen depth (basically FOV control, W/2 works best) gte_SetGeomScreen( CENTERX ); // Set light ambient color and light color matrix gte_SetBackColor( 63, 63, 63 ); gte_SetColorMatrix( &color_mtx ); // Init BIOS pad driver and set pad buffers (buffers are updated // automatically on every V-Blank) InitPAD(&pad_buff[0][0], 34, &pad_buff[1][0], 34); // Start pad StartPAD(); // Don't make pad driver acknowledge V-Blank IRQ (to avoid VSync timeout) ChangeClearPAD(0); // Load font and open a text stream FntLoad(960, 0); FntOpen(0, 8, 320, 216, 0, 100); } void display() { // Wait for GPU to finish drawing and vertical retrace DrawSync( 0 ); VSync( 0 ); // Swap buffers db_active ^= 1; db_nextpri = db[db_active].p; // Clear the OT of the next frame ClearOTagR( db[db_active].ot, OT_LEN ); // Apply display/drawing environments PutDrawEnv( &db[db_active].draw ); PutDispEnv( &db[db_active].disp ); // Enable display SetDispMask( 1 ); // Start drawing the OT of the last buffer DrawOTag( db[1-db_active].ot+(OT_LEN-1) ); }