Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
duarteapcoelho | ca63ef88ea | |
duarteapcoelho | f18108eaee | |
duarteapcoelho | df09c12c07 | |
duarteapcoelho | 3f1ea2cc5d | |
duarteapcoelho | 98f79eeb05 | |
duarteapcoelho | 332771104f | |
duarteapcoelho | 136e536544 |
|
@ -8,7 +8,7 @@ A 3D, multiplayer racing game for casio fx-CG50 calculators
|
|||
## Features
|
||||
- 3D graphics
|
||||
- Simple multiplayer (just connect two calculators)
|
||||
- Runs at about 16 FPS normally and 21 FPS overclocked, on the fx-CG50.
|
||||
- The multiplayer version runs at about 14 FPS and the singleplayer version runs at 24 FPS
|
||||
|
||||
## Controls
|
||||
- Press `up`/`8` to accelerate and `down`/`5` to brake
|
||||
|
@ -47,21 +47,18 @@ This version supports multiplayer, but it's slower than the gint version.
|
|||
This version doesn't support multiplayer, but it runs faster and doesn't have a border.
|
||||
#### Linux
|
||||
- Install gint ([https://gitea.planet-casio.com/Lephenixnoir/gint](https://gitea.planet-casio.com/Lephenixnoir/gint))
|
||||
- Install libprof ([https://gitea.planet-casio.com/Lephenixnoir/libprof](https://gitea.planet-casio.com/Lephenixnoir/libprof))
|
||||
- Run `make gint`
|
||||
|
||||
## Technical information
|
||||
### 3D rendering
|
||||
- All the rendering code is in `src/rasterizer.h` and `src/rasterizer.cpp`
|
||||
- Every triangle is clipped to avoid drawing triangles outside the screen. If a triangle is only partially inside the screen, it's cut in one or two triangles. This doesn't happen with the cones and the car to improve performance.
|
||||
- The triangles are rasterized using the scan line algorithm with a depth buffer.
|
||||
- The triangles are split into two (one with a flat top and another with a flat bottom) and rasterized.
|
||||
- This renderer only supports diffuse directional lighting, because this way there is only one color per triangle, which increases performance.
|
||||
- Because the calculator doesn't have a floating point unit (FPU), everything related to rendering uses fixed point numbers (defined in src/fp.h). This caused some issues related to precision, most of which were solved by checking where the floating point calculations were overflowing.
|
||||
- To improve performance, the cones that are too far away from the camera are replaced with a simpler model and the ones even further away aren't drawn at all.
|
||||
|
||||
#### Potential rendering performance improvements
|
||||
- Use DMA to clear the screen and draw the grass (in progress)
|
||||
- Clip models before clipping triangles
|
||||
|
||||
### Multiplayer
|
||||
All of the multiplayer code is in `src/main.cpp`
|
||||
- When the game starts, a second car is created outside the track.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
CC = sh-elf-g++
|
||||
CFLAGS += -Wall -Wextra -Ofast -funroll-loops -DGINT
|
||||
CFLAGS += -DFXCG50 -DTARGET_FXCG50 -m4-nofpu -mb -ffreestanding -nostdlib -fstrict-volatile-bitfields
|
||||
LDFLAGS = -m4-nofpu -mb -nostdlib -Wl,--no-warn-rwx-segments -T fxcg50.ld -lgint-cg -lc -lgcc -lgint-cg
|
||||
LDFLAGS = -m4-nofpu -mb -nostdlib -Wl,--no-warn-rwx-segments -T fxcg50.ld -lgint-cg -lc -lgcc -lgint-cg -lprof-cg
|
||||
|
||||
INCLUDES =
|
||||
|
||||
|
|
22
src/main.cpp
22
src/main.cpp
|
@ -28,13 +28,13 @@ vec3<float> cameraSpeed = {0, 0, 0};
|
|||
float cameraAngle = 0;
|
||||
|
||||
#ifdef GINT
|
||||
static GALIGNED(32) fp depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
|
||||
static GALIGNED(32) unsigned char depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
|
||||
#include "models.h"
|
||||
#endif
|
||||
|
||||
int main(){
|
||||
#ifndef GINT
|
||||
fp depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
|
||||
unsigned char depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
|
||||
#include "models.h"
|
||||
#endif
|
||||
Rasterizer::depthBuffer = depthBuffer;
|
||||
|
@ -62,6 +62,8 @@ int main(){
|
|||
Track::coneMesh = {22, cone_triangles};
|
||||
Track::simpleConeMesh = {2, simpleConeTriangles};
|
||||
|
||||
Time::init();
|
||||
|
||||
Display::init();
|
||||
Display::clear(newColor(70, 180, 220));
|
||||
Display::show();
|
||||
|
@ -77,14 +79,14 @@ int main(){
|
|||
{-1, -1, 0},
|
||||
{-1, 1, 0},
|
||||
{1, -1, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, -1},
|
||||
newColor(255, 255, 0)
|
||||
},
|
||||
{
|
||||
{-1, 1, 0},
|
||||
{1, 1, 0},
|
||||
{1, -1, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, -1},
|
||||
newColor(255, 255, 0)
|
||||
},
|
||||
};
|
||||
|
@ -137,6 +139,7 @@ int main(){
|
|||
#endif
|
||||
|
||||
#ifdef PRIZM
|
||||
Serial_Close(1);
|
||||
while(Serial_IsOpen() != 1){
|
||||
unsigned char mode[6] = {0, 5, 0, 0, 0, 0}; // 9600 bps 8n1
|
||||
Serial_Open(mode);
|
||||
|
@ -189,6 +192,9 @@ int main(){
|
|||
#ifdef PRIZM
|
||||
while(Input::keyDown(KEY_MENU))
|
||||
Input::update();
|
||||
|
||||
Serial_Close(1);
|
||||
|
||||
timer = Timer_Install(0, []() {
|
||||
Keyboard_PutKeycode(4, 9, 0);
|
||||
Timer_Stop(timer);
|
||||
|
@ -199,6 +205,14 @@ int main(){
|
|||
Bdisp_EnableColor(1);
|
||||
GetKey(&k);
|
||||
|
||||
Serial_Close(1);
|
||||
while(Serial_IsOpen() != 1){
|
||||
unsigned char mode[6] = {0, 5, 0, 0, 0, 0}; // 9600 bps 8n1
|
||||
Serial_Open(mode);
|
||||
}
|
||||
Serial_ClearTX();
|
||||
Serial_ClearRX();
|
||||
|
||||
continue;
|
||||
#endif
|
||||
#ifdef SDL
|
||||
|
|
|
@ -37,7 +37,7 @@ inline int max(int a, int b){
|
|||
namespace Rasterizer {
|
||||
Plane clippingPlanes[5];
|
||||
|
||||
fp *depthBuffer;
|
||||
unsigned char *depthBuffer;
|
||||
|
||||
fp fov_d = 1;
|
||||
|
||||
|
@ -50,14 +50,21 @@ namespace Rasterizer {
|
|||
}
|
||||
|
||||
void reset(){
|
||||
unsigned char value = -1;
|
||||
#if GINT || PRIZM
|
||||
long v = value | (value << 8) | (value << 16) | (value << 24);
|
||||
#endif
|
||||
#if GINT && PIXEL_SIZE == 1
|
||||
fp v = -1;
|
||||
fp *depthBuffer_P1 = (fp*) mmu_translate_uram(depthBuffer);
|
||||
cache_ocbp(depthBuffer, RENDER_WIDTH*RENDER_HEIGHT*sizeof(fp));
|
||||
dma_memset(depthBuffer_P1, *((uint32_t*)&v), RENDER_WIDTH*RENDER_HEIGHT*sizeof(fp));
|
||||
unsigned char *depthBuffer_P1 = (unsigned char*) mmu_translate_uram(depthBuffer);
|
||||
cache_ocbp(depthBuffer, RENDER_WIDTH*RENDER_HEIGHT*sizeof(unsigned char));
|
||||
dma_memset(depthBuffer_P1, *((uint32_t*)&v), RENDER_WIDTH*RENDER_HEIGHT*sizeof(unsigned char));
|
||||
#elif PRIZM
|
||||
for(int i = 0; i < RENDER_WIDTH*RENDER_HEIGHT/4; i++){
|
||||
*(((long*)depthBuffer) + i) = v;
|
||||
}
|
||||
#else
|
||||
for(int i = 0; i < RENDER_WIDTH*RENDER_HEIGHT; i++){
|
||||
depthBuffer[i] = -1;
|
||||
depthBuffer[i] = value;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -98,7 +105,7 @@ namespace Rasterizer {
|
|||
}
|
||||
|
||||
// Draws a triangle which has a horizontal top or bottom
|
||||
inline void _drawFlatSideTriangle(vec3<int> points[3], fp z, Color color, bool useDepth){
|
||||
inline void _drawFlatSideTriangle(vec3<int> points[3], unsigned char z, Color color, bool useDepth){
|
||||
if(points[0].y == points[1].y && points[1].y == points[2].y && points[2].y == points[0].y){
|
||||
return;
|
||||
}
|
||||
|
@ -141,7 +148,7 @@ namespace Rasterizer {
|
|||
#endif
|
||||
|
||||
for(int x = minX; x <= maxX; x++){
|
||||
if(z < depthBuffer[p] || depthBuffer[p] == fp(-1) || !useDepth){
|
||||
if(z < depthBuffer[p] || !useDepth){
|
||||
if(useDepth){
|
||||
depthBuffer[p] = z;
|
||||
}
|
||||
|
@ -177,11 +184,7 @@ namespace Rasterizer {
|
|||
toScreen(p2_d),
|
||||
};
|
||||
|
||||
if(dot(mat4::toMat3(model->viewMatrix) * mat4::toMat3(model->modelMatrix) * triangle.normal, vec3<fp>(0, 0, 1)) > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
fp z = (points[0].z + points[1].z + points[2].z) / 3;
|
||||
unsigned char z = (points[0].z + points[1].z + points[2].z) / 3;
|
||||
|
||||
if(isShaded){
|
||||
fp brightness = dot(mat4::toMat3(model->modelMatrix) * triangle.normal, vec3<fp>(I_SQRT_3, -I_SQRT_3, -I_SQRT_3)) * fp(0.6) + fp(0.4);
|
||||
|
@ -353,6 +356,10 @@ namespace Rasterizer {
|
|||
}
|
||||
|
||||
inline void drawTriangle(Model *model, Triangle triangle, bool useDepth, bool isShaded, bool clipTriangles){
|
||||
if(dot(mat4::toMat3(model->viewMatrix) * mat4::toMat3(model->modelMatrix) * triangle.normal, vec3<fp>(0, 0, 1)) > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
triangle.p0 = model->viewMatrix * model->modelMatrix * triangle.p0;
|
||||
triangle.p1 = model->viewMatrix * model->modelMatrix * triangle.p1;
|
||||
triangle.p2 = model->viewMatrix * model->modelMatrix * triangle.p2;
|
||||
|
@ -362,12 +369,14 @@ namespace Rasterizer {
|
|||
}
|
||||
|
||||
int inside = 5;
|
||||
for(int i = 0; i < 5; i++){
|
||||
if(dot(clippingPlanes[i].n, triangle.p0) + clippingPlanes[i].d < 0
|
||||
|| dot(clippingPlanes[i].n, triangle.p1) + clippingPlanes[i].d < 0
|
||||
|| dot(clippingPlanes[i].n, triangle.p2) + clippingPlanes[i].d < 0){
|
||||
inside--;
|
||||
break;
|
||||
if(clipTriangles){
|
||||
for(int i = 0; i < 5; i++){
|
||||
if(dot(clippingPlanes[i].n, triangle.p0) + clippingPlanes[i].d < 0
|
||||
|| dot(clippingPlanes[i].n, triangle.p1) + clippingPlanes[i].d < 0
|
||||
|| dot(clippingPlanes[i].n, triangle.p2) + clippingPlanes[i].d < 0){
|
||||
inside--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,8 +398,28 @@ Model::Model(){
|
|||
}
|
||||
Model::Model(Mesh mesh){
|
||||
this->mesh = mesh;
|
||||
fp radius2 = 0;
|
||||
for(int i = 0; i < mesh.numTriangles; i++){
|
||||
fp d0 = mesh.triangles[i].p0.length2();
|
||||
fp d1 = mesh.triangles[i].p1.length2();
|
||||
fp d2 = mesh.triangles[i].p2.length2();
|
||||
radius2 = max(radius2, d0);
|
||||
radius2 = max(radius2, d1);
|
||||
radius2 = max(radius2, d2);
|
||||
}
|
||||
float i_radius = _isqrt(radius2);
|
||||
radius = 1.0f/i_radius;
|
||||
}
|
||||
void Model::draw(bool useDepth, bool isShaded, bool clipTriangles){
|
||||
if(!clipTriangles){
|
||||
vec3<fp> center = viewMatrix * modelMatrix * vec3<fp>(0, 0, 0);
|
||||
for(int i = 0; i < 5; i++){
|
||||
if(dot(Rasterizer::clippingPlanes[i].n, center) + Rasterizer::clippingPlanes[i].d < radius){
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < mesh.numTriangles; i++){
|
||||
Rasterizer::drawTriangle(this, mesh.triangles[i], useDepth, isShaded, clipTriangles);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ struct Mesh {
|
|||
};
|
||||
|
||||
class Model {
|
||||
fp radius;
|
||||
public:
|
||||
Mesh mesh;
|
||||
mat4 modelMatrix;
|
||||
|
@ -38,7 +39,7 @@ public:
|
|||
namespace Rasterizer {
|
||||
void init();
|
||||
void reset();
|
||||
extern fp *depthBuffer;
|
||||
extern unsigned char *depthBuffer;
|
||||
|
||||
extern fp fov_d;
|
||||
void setFOV(int fov);
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
#ifdef GINT
|
||||
#include "time.h"
|
||||
#include <gint/rtc.h>
|
||||
#include <libprof.h>
|
||||
|
||||
namespace Time {
|
||||
void init(){}
|
||||
prof_t prof;
|
||||
void init(){
|
||||
prof_init();
|
||||
prof = prof_make();
|
||||
}
|
||||
void update(){
|
||||
prof_leave(prof);
|
||||
|
||||
const float lastTime = time;
|
||||
time = rtc_ticks();
|
||||
delta = time - lastTime;
|
||||
delta = prof_time(prof) / 1000.0f / (1000.0f / 128.0f);
|
||||
|
||||
prof = prof_make();
|
||||
prof_enter(prof);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include <fxcg/rtc.h>
|
||||
|
||||
namespace Time {
|
||||
void init(){
|
||||
}
|
||||
void update(){
|
||||
const float lastTime = time;
|
||||
time = RTC_GetTicks();
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace Time {
|
||||
void init(){
|
||||
}
|
||||
void update(){
|
||||
const float lastTime = time;
|
||||
time = ((float)(SDL_GetTicks()) / (1000.0/128.0));
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
namespace Time {
|
||||
extern float time;
|
||||
extern float delta;
|
||||
void init();
|
||||
void update();
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue