Browse Source
First import, with bits of code taken from the MDL viewer prototype, split up into organized units.
tess_experiment
First import, with bits of code taken from the MDL viewer prototype, split up into organized units.
tess_experiment
commit
ea44f4e99c
17 changed files with 503 additions and 0 deletions
-
2.gitignore
-
25CMakeLists.txt
-
23CMakePresets.json
-
29asset.c
-
13asset.h
-
BINatlas-e1m1.tim
-
BINatlas-e2m2.tim
-
16common.h
-
131display.c
-
13display.h
-
124input.c
-
7input.h
-
34iso.xml
-
54main.c
-
4system.cnf
-
19time.c
-
9time.h
@ -0,0 +1,2 @@ |
|||||
|
.vscode/ |
||||
|
build/ |
||||
@ -0,0 +1,25 @@ |
|||||
|
# PSn00bSDK example CMake script |
||||
|
# (C) 2021 spicyjpeg - MPL licensed |
||||
|
|
||||
|
cmake_minimum_required(VERSION 3.20) |
||||
|
|
||||
|
project( |
||||
|
PSn00bSDK-ps1bsp |
||||
|
LANGUAGES C CXX ASM |
||||
|
VERSION 1.0.0 |
||||
|
DESCRIPTION "PS1 BSP Viewer" |
||||
|
HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk" |
||||
|
) |
||||
|
|
||||
|
file(GLOB _sources *.c) |
||||
|
psn00bsdk_add_executable(ps1bsp STATIC ${_sources}) |
||||
|
|
||||
|
psn00bsdk_target_incbin(ps1bsp PRIVATE tim_e1m1 atlas-e1m1.tim) |
||||
|
psn00bsdk_target_incbin(ps1bsp PRIVATE tim_e2m2 atlas-e2m2.tim) |
||||
|
|
||||
|
psn00bsdk_add_cd_image( |
||||
|
iso # Target name |
||||
|
ps1bsp # Output file name (= ps1bsp.bin + ps1bsp.cue) |
||||
|
iso.xml # Path to config file |
||||
|
DEPENDS ps1bsp |
||||
|
) |
||||
@ -0,0 +1,23 @@ |
|||||
|
{ |
||||
|
"version": 2, |
||||
|
"cmakeMinimumRequired": { |
||||
|
"major": 3, |
||||
|
"minor": 20, |
||||
|
"patch": 0 |
||||
|
}, |
||||
|
"configurePresets": [ |
||||
|
{ |
||||
|
"name": "default", |
||||
|
"displayName": "Default configuration", |
||||
|
"description": "Use this preset to build the project using PSn00bSDK.", |
||||
|
"generator": "Ninja", |
||||
|
"binaryDir": "${sourceDir}/build", |
||||
|
"cacheVariables": { |
||||
|
"CMAKE_BUILD_TYPE": "Debug", |
||||
|
"CMAKE_TOOLCHAIN_FILE": "$env{PSN00BSDK_LIBS}/cmake/sdk.cmake", |
||||
|
"PSN00BSDK_TC": "", |
||||
|
"PSN00BSDK_TARGET": "mipsel-none-elf" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
#include "common.h" |
||||
|
#include "asset.h" |
||||
|
|
||||
|
void asset_loadTexture(const u_long* tim, ps1texture_t *texture) |
||||
|
{ |
||||
|
TIM_IMAGE* tparam; |
||||
|
GetTimInfo(tim, tparam); |
||||
|
|
||||
|
// This freezes up if the TIM image has weird dimensions |
||||
|
LoadImage(tparam->prect, tparam->paddr); |
||||
|
|
||||
|
// Upload CLUT for palettized images |
||||
|
if (tparam->mode & 0x8) |
||||
|
{ |
||||
|
LoadImage(tparam->crect, tparam->caddr); |
||||
|
} |
||||
|
|
||||
|
DrawSync(0); |
||||
|
|
||||
|
if (texture != NULL) |
||||
|
{ |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
#ifndef __ASSET_H__ |
||||
|
#define __ASSET_H__ |
||||
|
|
||||
|
typedef struct |
||||
|
{ |
||||
|
RECT prect, crect; |
||||
|
int mode; |
||||
|
int uoffs, voffs; |
||||
|
} ps1texture_t; |
||||
|
|
||||
|
void asset_loadTexture(const u_long* tim, ps1texture_t *texture); |
||||
|
|
||||
|
#endif // __ASSET_H__ |
||||
@ -0,0 +1,16 @@ |
|||||
|
#ifndef __COMMON_H__ |
||||
|
#define __COMMON_H__ |
||||
|
|
||||
|
#include <stdio.h> |
||||
|
#include <sys/types.h> |
||||
|
#include <psxapi.h> |
||||
|
#include <psxetc.h> |
||||
|
#include <psxgte.h> |
||||
|
#include <psxgpu.h> |
||||
|
|
||||
|
#define RotMatrixQ RotMatrix // TODO: temporary hack to allow Quake-specific code without implementation |
||||
|
|
||||
|
extern VECTOR cam_pos; |
||||
|
extern SVECTOR cam_rot; |
||||
|
|
||||
|
#endif // __COMMON_H__ |
||||
@ -0,0 +1,131 @@ |
|||||
|
#include "common.h" |
||||
|
#include "display.h" |
||||
|
|
||||
|
#include <inline_c.h> |
||||
|
|
||||
|
// Define display/draw environments for double buffering |
||||
|
static DISPENV disp[2]; |
||||
|
static DRAWENV draw[2]; |
||||
|
static 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. |
||||
|
// Layer 0 is free for overlays, as polygons at depth 0 will be clipped. |
||||
|
char primbuff[2][65536]; // Primitive buffer, just a raw buffer of bytes to use as a pool for primitives |
||||
|
char *nextpri; |
||||
|
|
||||
|
const MATRIX identity = { |
||||
|
ONE, 0, 0, |
||||
|
0, ONE, 0, |
||||
|
0, 0, ONE, |
||||
|
0, 0, 0, |
||||
|
}; |
||||
|
|
||||
|
// Transform to change the coordinate system from PS1 Default (Y down, Z forward) to Quake (Z up, Y forward) |
||||
|
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 }; |
||||
|
|
||||
|
void display_init() |
||||
|
{ |
||||
|
// 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, 448); |
||||
|
|
||||
|
// Open up a test font text stream |
||||
|
FntOpen(0, 8, SCREENWIDTH, SCREENHEIGHT, 0, 200); |
||||
|
|
||||
|
// TODO: OT initialization |
||||
|
|
||||
|
// Initialize GTE |
||||
|
InitGeom(); |
||||
|
gte_SetGeomOffset(SCREENWIDTH >> 1, SCREENHEIGHT >> 1); |
||||
|
gte_SetGeomScreen(180); // Screen depth for FOV control. Determines the distance of the camera to the near plane. |
||||
|
} |
||||
|
|
||||
|
void display_start() |
||||
|
{ |
||||
|
ClearOTagR(ot[db], OTLEN); |
||||
|
|
||||
|
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 = { -cam_rot.vx, -cam_rot.vy, -cam_rot.vz, 0 }; // Inverse camera rotation (in Quake coordinates) |
||||
|
VECTOR tpos = { -cam_pos.vx, -cam_pos.vy, -cam_pos.vz }; // Inverse camera position (in Quake coordinates) |
||||
|
RotMatrixQ(&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); |
||||
|
} |
||||
|
|
||||
|
void display_finish() |
||||
|
{ |
||||
|
// Flip buffer index |
||||
|
db = !db; |
||||
|
|
||||
|
// Wait for all drawing to complete |
||||
|
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); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
#ifndef __DISPLAY_H__ |
||||
|
#define __DISPLAY_H__ |
||||
|
|
||||
|
#define SCREENWIDTH 512 |
||||
|
#define SCREENHEIGHT 240 |
||||
|
|
||||
|
#define OTLEN 1024 |
||||
|
|
||||
|
void display_init(); |
||||
|
void display_start(); |
||||
|
void display_finish(); |
||||
|
|
||||
|
#endif // __DISPLAY_H__ |
||||
@ -0,0 +1,124 @@ |
|||||
|
#include "common.h" |
||||
|
#include "input.h" |
||||
|
|
||||
|
#include <psxpad.h> |
||||
|
|
||||
|
static u_char padBuffer[2][34]; |
||||
|
|
||||
|
static const int moveSpeed = 32; |
||||
|
static const short rotSpeed = 32; |
||||
|
static const char deadZone = 0x30; |
||||
|
|
||||
|
void input_init() |
||||
|
{ |
||||
|
InitPAD(padBuffer[0], 34, padBuffer[1], 34); |
||||
|
padBuffer[0][0] = padBuffer[0][1] = 0xFF; |
||||
|
padBuffer[1][0] = padBuffer[1][1] = 0xFF; |
||||
|
StartPAD(); |
||||
|
ChangeClearPAD(1); // Avoid issues with VSync timeouts caused by StartPAD |
||||
|
} |
||||
|
|
||||
|
void input_process() |
||||
|
{ |
||||
|
// Check if we have a connected controller on port 1 |
||||
|
if (padBuffer[0][0] != 0) |
||||
|
return; |
||||
|
|
||||
|
// Determine direction vectors of the camera |
||||
|
MATRIX cam_mtx; |
||||
|
RotMatrixQ(&cam_rot, &cam_mtx); |
||||
|
|
||||
|
u_short buttons = *((u_short*)(padBuffer[0]+2)); |
||||
|
if (!(buttons & PAD_UP)) |
||||
|
{ |
||||
|
// Move forward (+Y) |
||||
|
cam_pos.vx += (cam_mtx.m[0][1] * moveSpeed) >> 12; |
||||
|
cam_pos.vy += (cam_mtx.m[1][1] * moveSpeed) >> 12; |
||||
|
cam_pos.vz += (cam_mtx.m[2][1] * moveSpeed) >> 12; |
||||
|
} |
||||
|
if (!(buttons & PAD_DOWN)) |
||||
|
{ |
||||
|
// Move backward (-Y) |
||||
|
cam_pos.vx -= (cam_mtx.m[0][1] * moveSpeed) >> 12; |
||||
|
cam_pos.vy -= (cam_mtx.m[1][1] * moveSpeed) >> 12; |
||||
|
cam_pos.vz -= (cam_mtx.m[2][1] * moveSpeed) >> 12; |
||||
|
} |
||||
|
if (!(buttons & PAD_LEFT)) |
||||
|
{ |
||||
|
// Rotate left |
||||
|
cam_rot.vz += rotSpeed; |
||||
|
} |
||||
|
if (!(buttons & PAD_RIGHT)) |
||||
|
{ |
||||
|
// Rotate right |
||||
|
cam_rot.vz -= rotSpeed; |
||||
|
} |
||||
|
if (!(buttons & PAD_TRIANGLE)) |
||||
|
{ |
||||
|
// Move up (+Z) |
||||
|
cam_pos.vx += (cam_mtx.m[0][2] * moveSpeed) >> 12; |
||||
|
cam_pos.vy += (cam_mtx.m[1][2] * moveSpeed) >> 12; |
||||
|
cam_pos.vz += (cam_mtx.m[2][2] * moveSpeed) >> 12; |
||||
|
} |
||||
|
if (!(buttons & PAD_CROSS)) |
||||
|
{ |
||||
|
// Move down (-Z) |
||||
|
cam_pos.vx -= (cam_mtx.m[0][2] * moveSpeed) >> 12; |
||||
|
cam_pos.vy -= (cam_mtx.m[1][2] * moveSpeed) >> 12; |
||||
|
cam_pos.vz -= (cam_mtx.m[2][2] * moveSpeed) >> 12; |
||||
|
} |
||||
|
if (!(buttons & PAD_L1)) |
||||
|
{ |
||||
|
// Strafe left (-X) |
||||
|
cam_pos.vx -= (cam_mtx.m[0][0] * moveSpeed) >> 12; |
||||
|
cam_pos.vy -= (cam_mtx.m[1][0] * moveSpeed) >> 12; |
||||
|
cam_pos.vz -= (cam_mtx.m[2][0] * moveSpeed) >> 12; |
||||
|
} |
||||
|
if (!(buttons & PAD_R1)) |
||||
|
{ |
||||
|
// Strafe right (+X) |
||||
|
cam_pos.vx += (cam_mtx.m[0][0] * moveSpeed) >> 12; |
||||
|
cam_pos.vy += (cam_mtx.m[1][0] * moveSpeed) >> 12; |
||||
|
cam_pos.vz += (cam_mtx.m[2][0] * moveSpeed) >> 12; |
||||
|
} |
||||
|
if (!(buttons & PAD_L2)) |
||||
|
{ |
||||
|
// Look down |
||||
|
cam_rot.vx -= rotSpeed; |
||||
|
} |
||||
|
if (!(buttons & PAD_R2)) |
||||
|
{ |
||||
|
// Look up |
||||
|
cam_rot.vx += rotSpeed; |
||||
|
} |
||||
|
|
||||
|
// Check for analog controller |
||||
|
u_char padType = padBuffer[0][1] >> 4; |
||||
|
if (padType == 0x5 || padType == 0x7) |
||||
|
{ |
||||
|
short rightJoyX = (short)(padBuffer[0][4]) - 0x80; |
||||
|
if (rightJoyX > -deadZone && rightJoyX < deadZone) rightJoyX = 0; |
||||
|
short rightJoyY = (short)(padBuffer[0][5]) - 0x80; |
||||
|
if (rightJoyY > -deadZone && rightJoyY < deadZone) rightJoyY = 0; |
||||
|
short leftJoyX = (short)(padBuffer[0][6]) - 0x80; |
||||
|
if (leftJoyX > -deadZone && leftJoyX < deadZone) leftJoyX = 0; |
||||
|
short leftJoyY = (short)(padBuffer[0][7]) - 0x80; |
||||
|
if (leftJoyY > -deadZone && leftJoyY < deadZone) leftJoyY = 0; |
||||
|
|
||||
|
// Move forward/backward |
||||
|
cam_pos.vx -= (cam_mtx.m[0][1] * leftJoyY * moveSpeed) >> 19; |
||||
|
cam_pos.vy -= (cam_mtx.m[1][1] * leftJoyY * moveSpeed) >> 19; |
||||
|
cam_pos.vz -= (cam_mtx.m[2][1] * leftJoyY * moveSpeed) >> 19; |
||||
|
|
||||
|
// Strafe left/right |
||||
|
cam_pos.vx += (cam_mtx.m[0][0] * leftJoyX * moveSpeed) >> 19; |
||||
|
cam_pos.vy += (cam_mtx.m[1][0] * leftJoyX * moveSpeed) >> 19; |
||||
|
cam_pos.vz += (cam_mtx.m[2][0] * leftJoyX * moveSpeed) >> 19; |
||||
|
|
||||
|
// Rotate left/right |
||||
|
cam_rot.vz -= (rightJoyX * rotSpeed) >> 7; |
||||
|
|
||||
|
// Look up/down |
||||
|
cam_rot.vx -= (rightJoyY * rotSpeed) >> 7; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
#ifndef __INPUT_H__ |
||||
|
#define __INPUT_H__ |
||||
|
|
||||
|
void input_init(); |
||||
|
void input_process(); |
||||
|
|
||||
|
#endif // __INPUT_H__ |
||||
@ -0,0 +1,34 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<!-- |
||||
|
This file is processed by CMake and used by mkpsxiso to build the CD image. |
||||
|
|
||||
|
NOTE: all paths are relative to the build directory; if you want to include |
||||
|
a file from the source tree, you'll have to prepend its path with |
||||
|
${PROJECT_SOURCE_DIR}. |
||||
|
--> |
||||
|
<iso_project |
||||
|
image_name="${CD_IMAGE_NAME}.bin" |
||||
|
cue_sheet="${CD_IMAGE_NAME}.cue" |
||||
|
> |
||||
|
<track type="data"> |
||||
|
<identifiers |
||||
|
system ="PLAYSTATION" |
||||
|
volume ="PSN00BSDK_PS1BSP" |
||||
|
volume_set ="PSN00BSDK_PS1BSP" |
||||
|
publisher ="MEIDOTEK" |
||||
|
data_preparer ="PSN00BSDK ${PSN00BSDK_VERSION}" |
||||
|
application ="PLAYSTATION" |
||||
|
copyright ="README.TXT;1" |
||||
|
/> |
||||
|
|
||||
|
<directory_tree> |
||||
|
<file name="SYSTEM.CNF" type="data" source="${PROJECT_SOURCE_DIR}/system.cnf" /> |
||||
|
<file name="PS1BSP.EXE" type="data" source="ps1bsp.exe" /> |
||||
|
<file name="PS1BSP.MAP" type="data" source="ps1bsp.map" /> |
||||
|
|
||||
|
<dummy sectors="1024"/> |
||||
|
</directory_tree> |
||||
|
</track> |
||||
|
|
||||
|
<!--<track type="audio" source="track2.wav" />--> |
||||
|
</iso_project> |
||||
@ -0,0 +1,54 @@ |
|||||
|
#include "common.h" |
||||
|
#include "input.h" |
||||
|
#include "display.h" |
||||
|
#include "time.h" |
||||
|
#include "asset.h" |
||||
|
|
||||
|
extern u_long tim_e1m1[]; |
||||
|
extern u_long tim_e2m2[]; |
||||
|
|
||||
|
VECTOR cam_pos = { 0, -400, 100 }; |
||||
|
SVECTOR cam_rot = { 0 }; |
||||
|
|
||||
|
// BSP face rendering: |
||||
|
// - Gather vertex data from face start index + length |
||||
|
// - Store vertex data in scratchpad memory (should be plenty of space for one face) |
||||
|
// - Perform camera offset and re-scaling operations to fit vertex data in signed 16-bit fixed point vectors |
||||
|
// - Note: may be possible to (ab)use 32-bit GTE registers & ops to do this calculation and downcast |
||||
|
// - Store rescaled vertex data in scratchpad memory |
||||
|
|
||||
|
// Init function |
||||
|
void init(void) |
||||
|
{ |
||||
|
time_init(); |
||||
|
input_init(); |
||||
|
display_init(); |
||||
|
|
||||
|
asset_loadTexture(tim_e1m1, NULL); |
||||
|
asset_loadTexture(tim_e2m2, NULL); |
||||
|
} |
||||
|
|
||||
|
// Main function, program entrypoint |
||||
|
int main(int argc, const char *argv[]) |
||||
|
{ |
||||
|
// Init stuff |
||||
|
init(); |
||||
|
|
||||
|
// Main loop |
||||
|
while(1) |
||||
|
{ |
||||
|
input_process(); |
||||
|
|
||||
|
display_start(); |
||||
|
|
||||
|
// Draw stuff |
||||
|
FntPrint(-1, "Camera pos = (%d, %d, %d) rot = (%d, %d, %d)\n", cam_pos.vx, cam_pos.vy, cam_pos.vz, cam_rot.vx, cam_rot.vy, cam_rot.vz); |
||||
|
FntFlush(-1); |
||||
|
|
||||
|
display_finish(); |
||||
|
|
||||
|
time_tick(); |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
BOOT=cdrom:\ps1bsp.exe;1 |
||||
|
TCB=4 |
||||
|
EVENT=10 |
||||
|
STACK=801FFFF0 |
||||
@ -0,0 +1,19 @@ |
|||||
|
#include "common.h" |
||||
|
#include "time.h" |
||||
|
|
||||
|
static int frameNumber; |
||||
|
|
||||
|
void time_init() |
||||
|
{ |
||||
|
frameNumber = 0; |
||||
|
} |
||||
|
|
||||
|
void time_tick() |
||||
|
{ |
||||
|
++frameNumber; |
||||
|
} |
||||
|
|
||||
|
int time_getFrameNumber() |
||||
|
{ |
||||
|
return frameNumber; |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
#ifndef __TIME_H__ |
||||
|
#define __TIME_H__ |
||||
|
|
||||
|
void time_init(); |
||||
|
void time_tick(); |
||||
|
|
||||
|
int time_getFrameNumber(); |
||||
|
|
||||
|
#endif // __TIME_H__ |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue