Include C test program in repo

This commit is contained in:
jaburns
2020-10-19 22:17:32 -06:00
parent 82126eca3c
commit d3a2cbd2b2
14 changed files with 940 additions and 56 deletions
+2
View File
@@ -1,5 +1,7 @@
/src/mario
/build/
/dist/
/test/level.c
/test/level.h
*.z64
*.so
+21 -6
View File
@@ -1,16 +1,17 @@
default: all
default: test
CC := cc
CFLAGS := -g -Wall -fPIC
LDFLAGS := -lm -shared
SRC_DIRS := src src/engine src/game src/mario src/tools
SRC_DIRS := src src/engine src/game src/mario src/tools test
BUILD_DIR := build
DIST_DIR := dist
ALL_DIRS := $(addprefix $(BUILD_DIR)/,$(SRC_DIRS))
BIN_FILE := $(DIST_DIR)/libsm64.so
LIB_FILE := $(DIST_DIR)/libsm64.so
LIB_H_FILE := $(DIST_DIR)/include/libsm64.h
TEST_FILE := run-test
C_IMPORTED := src/mario/geo.inc.c src/mario/model.inc.c
H_IMPORTED := $(C_IMPORTED:.c=.h)
@@ -19,6 +20,7 @@ IMPORTED := $(C_IMPORTED) $(H_IMPORTED)
C_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) $(C_IMPORTED)
O_FILES := $(foreach file,$(C_FILES),$(BUILD_DIR)/$(file:.c=.o))
DEP_FILES := $(O_FILES:.o=.d)
TEST_OBJS := build/test/context.o build/test/level.o build/test/main.o
DUMMY != mkdir -p src/mario
DUMMY != mkdir -p $(ALL_DIRS)
@@ -28,19 +30,32 @@ $(filter-out src/mario/geo.inc.c,$(IMPORTED)): src/mario/geo.inc.c
src/mario/geo.inc.c: ./import-mario-geo.py
./import-mario-geo.py
test/level.c test/level.h: ./import-test-collision.py
./import-test-collision.py
test/main.c: test/level.h
$(BUILD_DIR)/%.o: %.c $(IMPORTED)
@$(CC) $(CFLAGS) -MM -MP -MT $@ -MF $(BUILD_DIR)/$*.d $<
$(CC) -c $(CFLAGS) -o $@ $<
$(BIN_FILE): $(O_FILES)
$(LIB_FILE): $(O_FILES)
$(CC) $(LDFLAGS) -o $@ $^
$(LIB_H_FILE): src/libsm64.h
cp -f $< $@
all: $(BIN_FILE) $(LIB_H_FILE)
$(TEST_FILE): $(LIB_FILE) $(TEST_OBJS)
$(CC) -o $@ $(TEST_OBJS) $(LIB_FILE) -lGLEW -lGL -lSDL2 -lSDL2main -lm
lib: $(LIB_FILE)
test: $(TEST_FILE)
run: test
./$(TEST_FILE)
clean:
rm -rf $(BUILD_DIR) $(DIST_DIR) src/mario
rm -rf $(BUILD_DIR) $(DIST_DIR) src/mario test/level.?
-include $(DEP_FILES)
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import os
import shutil
import requests
BOB_COLLISION_URL = "https://raw.githubusercontent.com/n64decomp/sm64/master/levels/bob/areas/1/collision.inc.c"
LEVEL_H = """#pragma once
#include "../src/libsm64.h"
extern const struct SM64Surface surfaces[];
extern const size_t surfaces_count;
"""
def main():
print("Downloading " + BOB_COLLISION_URL)
in_lines = requests.get(BOB_COLLISION_URL).text.splitlines()
verts = []
tris = []
mode = ""
for line in in_lines:
if not line.strip().startswith("COL_"):
continue;
tokens = line.strip().replace("(", ",").replace(")", "").split(",")
if tokens[0] == "COL_VERTEX":
verts.append([ int(tokens[1]), int(tokens[2]), int(tokens[3]) ])
elif tokens[0] == "COL_TRI_INIT":
mode = tokens[1]
elif tokens[0] == "COL_TRI":
tris.append([ int(tokens[1]), int(tokens[2]), int(tokens[3]), mode ])
out_lines = []
for tri in tris:
out_lines.append("{%s,0,{{%s,%s,%s},{%s,%s,%s},{%s,%s,%s}}}"%(tri[3], \
verts[tri[0]][0], verts[tri[0]][1], verts[tri[0]][2], \
verts[tri[1]][0], verts[tri[1]][1], verts[tri[1]][2], \
verts[tri[2]][0], verts[tri[2]][1], verts[tri[2]][2]))
out_str = ",\n".join(out_lines)
out_str = "const struct SM64Surface surfaces[] = {\n" + out_str + "};\n\n"
out_str += "const size_t surfaces_count = sizeof( surfaces ) / sizeof( surfaces[0] );"
out_str = '#include "../src/include/surface_terrains.h"\n' + out_str
out_str = '#include "level.h"\n' + out_str
with open("test/level.c", "w") as file:
file.write(out_str)
with open("test/level.h", "w") as file:
file.write(LEVEL_H)
if __name__ == "__main__":
main()
Executable
BIN
View File
Binary file not shown.
+4 -4
View File
@@ -1669,7 +1669,7 @@ void mario_update_hitbox_and_cap_model(struct MarioState *m) {
* An unused and possibly a debug function. Z + another button input
* sets Mario with a different cap.
*/
static void debug_update_mario_cap(u16 button, s32 flags, u16 capTimer, u16 capMusic) {
//static void debug_update_mario_cap(u16 button, s32 flags, u16 capTimer, u16 capMusic) {
// // This checks for Z_TRIG instead of Z_DOWN flag
// // (which is also what other debug functions do),
// // so likely debug behavior rather than unused behavior.
@@ -1683,7 +1683,7 @@ static void debug_update_mario_cap(u16 button, s32 flags, u16 capTimer, u16 capM
// play_cap_music(capMusic);
// }
}
//}
#ifdef VERSION_SH
void func_sh_8025574C(void) {
@@ -1793,8 +1793,8 @@ s32 execute_mario_action(UNUSED struct Object *o) {
**************************************************/
void init_mario(void) {
Vec3s capPos;
struct Object *capObject;
//Vec3s capPos;
//struct Object *capObject;
unused80339F10 = 0;
+41 -39
View File
@@ -60,22 +60,22 @@ enum SaveOption { SAVE_OPT_SAVE_AND_CONTINUE = 1, SAVE_OPT_SAVE_AND_QUIT, SAVE_O
static struct Object *sIntroWarpPipeObj;
static struct Object *sEndPeachObj;
static struct Object *sEndRightToadObj;
static struct Object *sEndLeftToadObj;
//static struct Object *sEndRightToadObj;
//static struct Object *sEndLeftToadObj;
static struct Object *sEndJumboStarObj;
static UNUSED s32 sUnused;
static s16 sEndPeachAnimation;
static s16 sEndToadAnims[2];
//static s16 sEndToadAnims[2];
static Vp sEndCutsceneVp = { { { 640, 480, 511, 0 }, { 640, 480, 511, 0 } } };
static struct CreditsEntry *sDispCreditsEntry = NULL;
//static struct CreditsEntry *sDispCreditsEntry = NULL;
// related to peach gfx?
static s8 D_8032CBE4 = 0;
static s8 D_8032CBE8 = 0;
static s8 D_8032CBEC[7] = { 2, 3, 2, 1, 2, 3, 2 };
//static s8 D_8032CBE8 = 0;
//static s8 D_8032CBEC[7] = { 2, 3, 2, 1, 2, 3, 2 };
static u8 sStarsNeededForDialog[] = { 1, 3, 8, 30, 50, 70 };
//static u8 sStarsNeededForDialog[] = { 1, 3, 8, 30, 50, 70 };
/**
* Data for the jumbo star cutscene. It specifies the flight path after triple
@@ -239,15 +239,15 @@ s32 geo_switch_peach_eyes(s32 run, struct GraphNode *node, UNUSED s32 a2) {
// }
// }
// return 0;
return 0;
}
// unused
static void stub_is_textbox_active(u16 *a0) {
//static void stub_is_textbox_active(u16 *a0) {
// if (get_dialog_id() == -1) {
// *a0 = 0;
// }
}
//}
/**
* get_star_collection_dialog: Determine what dialog should show when Mario
@@ -271,6 +271,7 @@ s32 get_star_collection_dialog(struct MarioState *m) {
// m->prevNumStarsForDialog = m->numStars;
// return dialogID;
return 0;
}
// save menu handler
@@ -318,6 +319,7 @@ struct Object *spawn_obj_at_mario_rel_yaw(struct MarioState *m, s32 model, const
// o->oPosZ = m->pos[2];
// return o;
return NULL;
}
/**
@@ -519,7 +521,7 @@ s32 act_reading_automatic_dialog(struct MarioState *m) {
// }
// // apply head turn
// vec3s_set(m->marioBodyState->headAngle, m->actionTimer, 0, 0);
// return FALSE;
return FALSE;
}
s32 act_reading_sign(struct MarioState *m) {
@@ -559,7 +561,7 @@ s32 act_reading_sign(struct MarioState *m) {
//
// vec3f_copy(marioObj->header.gfx.pos, m->pos);
// vec3s_set(marioObj->header.gfx.angle, 0, m->faceAngle[1], 0);
// return FALSE;
return FALSE;
}
s32 act_debug_free_move(struct MarioState *m) {
@@ -842,7 +844,7 @@ s32 act_unlocking_key_door(struct MarioState *m) {
// }
// m->actionTimer++;
// return FALSE;
return FALSE;
}
s32 act_unlocking_star_door(struct MarioState *m) {
@@ -883,7 +885,7 @@ s32 act_unlocking_star_door(struct MarioState *m) {
// update_mario_pos_for_anim(m);
// stop_and_set_height_to_floor(m);
// return FALSE;
return FALSE;
}
s32 act_entering_star_door(struct MarioState *m) {
@@ -1181,7 +1183,7 @@ s32 act_exit_land_save_dialog(struct MarioState *m) {
// }
// m->marioObj->header.gfx.angle[1] += 0x8000;
// return FALSE;
return FALSE;
}
s32 act_death_exit(struct MarioState *m) {
@@ -2011,21 +2013,21 @@ void generate_yellow_sparkles(s16 x, s16 y, s16 z, f32 radius) {
// not sure what this does, returns the height of the floor.
// (animation related?)
static f32 end_obj_set_visual_pos(struct Object *o) {
struct Surface *surf;
Vec3s sp24;
f32 sp20;
f32 sp1C;
f32 sp18;
find_mario_anim_flags_and_translation(o, o->header.gfx.angle[1], sp24);
sp20 = o->header.gfx.pos[0] + sp24[0];
sp1C = o->header.gfx.pos[1] + 10.0f;
sp18 = o->header.gfx.pos[2] + sp24[2];
return find_floor(sp20, sp1C, sp18, &surf);
}
// static f32 end_obj_set_visual_pos(struct Object *o) {
// struct Surface *surf;
// Vec3s sp24;
// f32 sp20;
// f32 sp1C;
// f32 sp18;
//
// find_mario_anim_flags_and_translation(o, o->header.gfx.angle[1], sp24);
//
// sp20 = o->header.gfx.pos[0] + sp24[0];
// sp1C = o->header.gfx.pos[1] + 10.0f;
// sp18 = o->header.gfx.pos[2] + sp24[2];
//
// return find_floor(sp20, sp1C, sp18, &surf);
// }
// make Mario fall and soften wing cap gravity
static void end_peach_cutscene_mario_falling(struct MarioState *m) {
@@ -2308,13 +2310,13 @@ static void end_peach_cutscene_dialog_2(struct MarioState *m) {
#undef TIMER_PEACH_KISS
// blink twice then have half-shut eyes (see end_peach_cutscene_kiss_from_peach)
static u8 sMarioBlinkOverride[20] = {
MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_CLOSED, MARIO_EYES_CLOSED,
MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_OPEN, MARIO_EYES_OPEN,
MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_CLOSED, MARIO_EYES_CLOSED,
MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_OPEN, MARIO_EYES_OPEN,
MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_CLOSED, MARIO_EYES_CLOSED,
};
// static u8 sMarioBlinkOverride[20] = {
// MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_CLOSED, MARIO_EYES_CLOSED,
// MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_OPEN, MARIO_EYES_OPEN,
// MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_CLOSED, MARIO_EYES_CLOSED,
// MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_OPEN, MARIO_EYES_OPEN,
// MARIO_EYES_HALF_CLOSED, MARIO_EYES_HALF_CLOSED, MARIO_EYES_CLOSED, MARIO_EYES_CLOSED,
// };
static void end_peach_cutscene_kiss_from_peach(struct MarioState *m) {
// sEndPeachAnimation = 10;
@@ -2610,7 +2612,7 @@ static s32 act_credits_cutscene(struct MarioState *m) {
//
// m->marioObj->header.gfx.angle[1] += (gCurrCreditsEntry->unk02 & 0xC0) << 8;
//
// return FALSE;
return FALSE;
}
static s32 act_end_waving_cutscene(struct MarioState *m) {
@@ -2648,7 +2650,7 @@ static s32 act_end_waving_cutscene(struct MarioState *m) {
// level_trigger_warp(m, WARP_OP_CREDITS_END);
// }
// return FALSE;
return FALSE;
}
static s32 check_for_instant_quicksand(struct MarioState *m) {
+4 -3
View File
@@ -223,7 +223,7 @@ void bhv_toad_message_init(void) {
// }
}
static void star_door_unlock_spawn_particles(s16 angleOffset) {
//static void star_door_unlock_spawn_particles(s16 angleOffset) {
// struct Object *sparkleParticle = spawn_object(gCurrentObject, 0, bhvSparkleSpawn);
//
// sparkleParticle->oPosX +=
@@ -232,7 +232,7 @@ static void star_door_unlock_spawn_particles(s16 angleOffset) {
// 100.0f * coss((gCurrentObject->oUnlockDoorStarTimer * 0x2800) + angleOffset);
// // Particles are spawned lower each frame
// sparkleParticle->oPosY -= gCurrentObject->oUnlockDoorStarTimer * 10.0f;
}
//}
void bhv_unlock_door_star_init(void) {
// gCurrentObject->oUnlockDoorStarState = UNLOCK_DOOR_STAR_RISING;
@@ -410,7 +410,7 @@ Gfx *geo_mario_tilt_torso(s32 callContext, struct GraphNode *node, UNUSED Mat4 *
Gfx *geo_mario_head_rotation(s32 callContext, struct GraphNode *node, UNUSED Mat4 *c) {
struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node;
struct MarioBodyState *bodyState = &gBodyStates[asGenerated->parameter];
s32 action = bodyState->action;
// s32 action = bodyState->action;
if (callContext == GEO_CONTEXT_RENDER) {
struct GraphNodeRotation *rotNode = (struct GraphNodeRotation *) node->next;
@@ -653,4 +653,5 @@ Gfx *geo_mirror_mario_backface_culling(s32 callContext, struct GraphNode *node,
// asGenerated->fnNode.node.flags = (asGenerated->fnNode.node.flags & 0xFF) | (LAYER_OPAQUE << 8);
// }
// return gfx;
return NULL;
}
+1 -1
View File
@@ -239,7 +239,7 @@ struct Object *hack_allocate_mario(void)
*/
void bhv_mario_update(void) {
u32 particleFlags = 0;
s32 i;
// s32 i;
gCurrentObject = gMarioObject;
particleFlags = execute_mario_action(gCurrentObject);
+3 -3
View File
@@ -27,8 +27,8 @@ enum MarioTextures
#define NUM_USED_TEXTURES 11
static int mario_tex_offsets[NUM_USED_TEXTURES] = { 144, 4240, 6288, 8336, 10384, 12432, 14480, 16528, 30864, 32912, 37008 };
static int mario_tex_widths [NUM_USED_TEXTURES] = { 64, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32 };
static int mario_tex_heights[NUM_USED_TEXTURES] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 64 };
static const int mario_tex_offsets[NUM_USED_TEXTURES] = { 144, 4240, 6288, 8336, 10384, 12432, 14480, 16528, 30864, 32912, 37008 };
static const int mario_tex_widths [NUM_USED_TEXTURES] = { 64, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32 };
static const int mario_tex_heights[NUM_USED_TEXTURES] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 64 };
extern void load_mario_textures_from_rom( uint8_t *rom, uint8_t *outTexture );
+161
View File
@@ -0,0 +1,161 @@
/////////////////////////////////////////////////////////////////////
// A few choice functions and types from the cglm library
// https://github.com/recp/cglm
//
#pragma once
#include <math.h>
#include <string.h>
typedef float vec2[2];
typedef float vec3[3];
typedef float vec4[4];
typedef float mat4[4][4];
static void
glm_mat4_zero( mat4 m )
{
memset( m, 0, sizeof(mat4) );
}
static void
glm_mat4_identity( mat4 m )
{
glm_mat4_zero( m );
m[0][0] = 1.0f;
m[1][1] = 1.0f;
m[2][2] = 1.0f;
m[3][3] = 1.0f;
}
static void
glm_vec4_scale(vec4 v, float s, vec4 dest) {
dest[0] = v[0] * s;
dest[1] = v[1] * s;
dest[2] = v[2] * s;
dest[3] = v[3] * s;
}
static void
glm_vec4_add(vec4 a, vec4 b, vec4 dest) {
dest[0] = a[0] + b[0];
dest[1] = a[1] + b[1];
dest[2] = a[2] + b[2];
dest[3] = a[3] + b[3];
}
static void
glm_vec3_scale(vec3 v, float s, vec3 dest) {
dest[0] = v[0] * s;
dest[1] = v[1] * s;
dest[2] = v[2] * s;
}
static void
glm_vec3_sub(vec3 a, vec3 b, vec3 dest) {
dest[0] = a[0] - b[0];
dest[1] = a[1] - b[1];
dest[2] = a[2] - b[2];
}
static float
glm_vec3_dot(vec3 a, vec3 b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
static float
glm_vec3_norm2(vec3 v) {
return glm_vec3_dot(v, v);
}
static float
glm_vec3_norm(vec3 v) {
return sqrtf(glm_vec3_norm2(v));
}
static void
glm_vec3_normalize(vec3 v) {
float norm;
norm = glm_vec3_norm(v);
if (norm == 0.0f) {
v[0] = v[1] = v[2] = 0.0f;
return;
}
glm_vec3_scale(v, 1.0f / norm, v);
}
static void
glm_vec3_cross(vec3 a, vec3 b, vec3 dest) {
/* (u2.v3 - u3.v2, u3.v1 - u1.v3, u1.v2 - u2.v1) */
dest[0] = a[1] * b[2] - a[2] * b[1];
dest[1] = a[2] * b[0] - a[0] * b[2];
dest[2] = a[0] * b[1] - a[1] * b[0];
}
static void
glm_vec3_crossn(vec3 a, vec3 b, vec3 dest) {
glm_vec3_cross(a, b, dest);
glm_vec3_normalize(dest);
}
static void
glm_perspective(float fovy,
float aspect,
float nearVal,
float farVal,
mat4 dest) {
float f, fn;
glm_mat4_zero(dest);
f = 1.0f / tanf(fovy * 0.5f);
fn = 1.0f / (nearVal - farVal);
dest[0][0] = f / aspect;
dest[1][1] = f;
dest[2][2] = (nearVal + farVal) * fn;
dest[2][3] =-1.0f;
dest[3][2] = 2.0f * nearVal * farVal * fn;
}
static void
glm_translate(mat4 m, vec3 v) {
vec4 v1, v2, v3;
glm_vec4_scale(m[0], v[0], v1);
glm_vec4_scale(m[1], v[1], v2);
glm_vec4_scale(m[2], v[2], v3);
glm_vec4_add(v1, m[3], m[3]);
glm_vec4_add(v2, m[3], m[3]);
glm_vec4_add(v3, m[3], m[3]);
}
static void
glm_lookat(vec3 eye, vec3 center, vec3 up, mat4 dest) {
vec3 f, u, s;
glm_vec3_sub(center, eye, f);
glm_vec3_normalize(f);
glm_vec3_crossn(f, up, s);
glm_vec3_cross(s, f, u);
dest[0][0] = s[0];
dest[0][1] = u[0];
dest[0][2] =-f[0];
dest[1][0] = s[1];
dest[1][1] = u[1];
dest[1][2] =-f[1];
dest[2][0] = s[2];
dest[2][1] = u[2];
dest[2][2] =-f[2];
dest[3][0] =-glm_vec3_dot(s, eye);
dest[3][1] =-glm_vec3_dot(u, eye);
dest[3][2] = glm_vec3_dot(f, eye);
dest[0][3] = dest[1][3] = dest[2][3] = 0.0f;
dest[3][3] = 1.0f;
}
+115
View File
@@ -0,0 +1,115 @@
#include "context.h"
#include <string.h>
static SDL_Window *s_sdlWindow;
static SDL_GLContext s_sdlGlContext;
static SDL_GameController *s_controller;
static int s_windowWidth;
static int s_windowHeight;
void context_init( const char *title, int width, int height )
{
if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK ) < 0 ) goto err;
SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 1 );
SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, 4 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 4 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetSwapInterval( 1 );
s_windowWidth = width;
s_windowHeight = height;
s_sdlWindow = SDL_CreateWindow(
title,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width, height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
);
if( !s_sdlWindow ) goto err;
s_sdlGlContext = SDL_GL_CreateContext( s_sdlWindow );
if( !s_sdlGlContext ) goto err;
#ifndef __APPLE__
glewExperimental = GL_TRUE;
const GLenum glewInitResult = glewInit();
if( glewInitResult != GLEW_OK ) goto err;
#endif
s_controller = NULL;
for( int i = 0; i < SDL_NumJoysticks(); ++i )
{
if( ! SDL_IsGameController( i ) ) continue;
s_controller = SDL_GameControllerOpen( i );
printf( "Using game controller: %s", SDL_GameControllerName( s_controller ) );
if( s_controller ) break;
}
return;
err:
SDL_GL_DeleteContext( s_sdlGlContext );
SDL_DestroyWindow( s_sdlWindow );
SDL_Quit();
}
SDL_GameController *context_get_controller( void )
{
return s_controller;
}
bool context_flip_frame_poll_events( void )
{
bool still_running = true;
SDL_GL_SwapWindow( s_sdlWindow );
SDL_Event event;
while( SDL_PollEvent( &event ) )
{
switch( event.type )
{
case SDL_QUIT:
still_running = false;
break;
case SDL_KEYDOWN:
if( event.key.keysym.sym == SDLK_ESCAPE )
still_running = false;
case SDL_WINDOWEVENT:
if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED )
{
s_windowWidth = event.window.data1;
s_windowHeight = event.window.data2;
glViewport( 0, 0, s_windowWidth, s_windowHeight );
}
break;
}
}
return still_running;
}
void context_terminate( void )
{
if( !s_sdlWindow )
return;
if( s_controller )
SDL_GameControllerClose( s_controller );
SDL_GL_DeleteContext( s_sdlGlContext );
SDL_DestroyWindow( s_sdlWindow );
SDL_Quit();
s_sdlWindow = NULL;
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <stdbool.h>
#ifdef __APPLE__
# include <OpenGL/gl3.h>
# include <SDL2/SDL.h>
#elif _WIN32
# define HAVE_M_PI
# include <GL/glew.h>
# include <SDL.h>
#else
# include <GL/glew.h>
# include <SDL2/SDL.h>
#endif
extern void context_init( const char *title, int width, int height );
extern bool context_flip_frame_poll_events( void );
extern SDL_GameController *context_get_controller( void );
extern void context_terminate( void );
+450
View File
@@ -0,0 +1,450 @@
#define _CRT_SECURE_NO_WARNINGS 1 // for fopen
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include "../src/libsm64.h"
#include "cglm.h"
#include "ns_clock.h"
#include "level.h"
#include "context.h"
static const int WINDOW_WIDTH = 1280;
static const int WINDOW_HEIGHT = 800;
typedef struct CollisionMesh
{
size_t num_vertices;
float *position;
float *normal;
uint16_t *index;
GLuint vao;
GLuint position_buffer;
GLuint normal_buffer;
}
CollisionMesh;
typedef struct MarioMesh
{
size_t num_vertices;
uint16_t *index;
GLuint vao;
GLuint position_buffer;
GLuint normal_buffer;
GLuint color_buffer;
GLuint uv_buffer;
}
MarioMesh;
typedef struct RenderState
{
CollisionMesh collision;
MarioMesh mario;
GLuint world_shader;
GLuint mario_shader;
GLuint mario_texture;
}
RenderState;
static const char *MARIO_SHADER =
"\n uniform mat4 view;"
"\n uniform mat4 projection;"
"\n uniform sampler2D marioTex;"
"\n "
"\n v2f vec3 v_color;"
"\n v2f vec3 v_normal;"
"\n v2f vec3 v_light;"
"\n v2f vec2 v_uv;"
"\n "
"\n #ifdef VERTEX"
"\n "
"\n layout(location = 0) in vec3 position;"
"\n layout(location = 1) in vec3 normal;"
"\n layout(location = 2) in vec3 color;"
"\n layout(location = 3) in vec2 uv;"
"\n "
"\n void main()"
"\n {"
"\n v_color = color;"
"\n v_normal = normal;"
"\n v_light = transpose( mat3( view )) * normalize( vec3( 1 ));"
"\n v_uv = uv;"
"\n "
"\n gl_Position = projection * view * vec4( position, 1. );"
"\n }"
"\n "
"\n #endif"
"\n #ifdef FRAGMENT"
"\n "
"\n out vec4 color;"
"\n "
"\n void main() "
"\n {"
"\n float light = .5 + .5 * clamp( dot( v_normal, v_light ), 0., 1. );"
"\n vec4 texColor = texture2D( marioTex, v_uv );"
"\n vec3 mainColor = mix( v_color, texColor.rgb, texColor.a ); // v_uv.x >= 0. ? texColor.a : 0. );"
"\n color = vec4( mainColor * light, 1 );"
"\n }"
"\n "
"\n #endif"
;
static const char *WORLD_SHADER =
"\n uniform mat4 model;"
"\n uniform mat4 view;"
"\n uniform mat4 projection;"
"\n uniform sampler2D tex;"
"\n "
"\n v2f vec3 v_normal;"
"\n v2f vec3 v_worldPos;"
"\n "
"\n #ifdef VERTEX"
"\n "
"\n layout(location = 0) in vec3 position;"
"\n layout(location = 1) in vec3 normal;"
"\n "
"\n void main()"
"\n {"
"\n v_normal = inverse(mat3(model)) * normal;"
"\n vec4 worldPos4 = model * vec4(position, 1.);"
"\n v_worldPos = worldPos4.xyz;"
"\n gl_Position = projection * view * worldPos4;"
"\n }"
"\n "
"\n #endif"
"\n #ifdef FRAGMENT"
"\n "
"\n vec3 tri( vec3 x )"
"\n {"
"\n return abs(x-floor(x)-.5);"
"\n } "
"\n float surfFunc( vec3 p )"
"\n {"
"\n float n = dot(tri(p*.15 + tri(p.yzx*.075)), vec3(.444));"
"\n p = p*1.5773 - n;"
"\n p.yz = vec2(p.y + p.z, p.z - p.y) * .866;"
"\n p.xz = vec2(p.x + p.z, p.z - p.x) * .866;"
"\n n += dot(tri(p*.225 + tri(p.yzx*.1125)), vec3(.222)); "
"\n return abs(n-.5)*1.9 + (1.-abs(sin(n*9.)))*.05;"
"\n }"
"\n "
"\n const vec3 light_x = vec3(-1.0, 0.4, 0.9);"
"\n "
"\n out vec4 color;"
"\n "
"\n void main() "
"\n {"
"\n float surfy = surfFunc( v_worldPos / 50. );"
"\n float brightness = smoothstep( .2, .3, surfy );"
"\n "
"\n color = vec4( (0.5 + 0.25 * brightness) * (.5+.5*v_normal), 1 );"
"\n }"
"\n "
"\n #endif"
;
uint8_t *utils_read_file_alloc( const char *path, size_t *fileLength )
{
FILE *f = fopen( path, "rb" );
if( !f ) return NULL;
fseek( f, 0, SEEK_END );
size_t length = (size_t)ftell( f );
rewind( f );
uint8_t *buffer = malloc( length + 1 );
fread( buffer, 1, length, f );
buffer[length] = 0;
fclose( f );
if( fileLength ) *fileLength = length;
return buffer;
}
static GLuint shader_compile( const char *shaderContents, size_t shaderContentsLength, GLenum shaderType )
{
const GLchar *shaderDefine = shaderType == GL_VERTEX_SHADER
? "\n#version 410\n#define VERTEX \n#define v2f out\n"
: "\n#version 410\n#define FRAGMENT\n#define v2f in \n";
const GLchar *shaderStrings[2] = { shaderDefine, shaderContents };
GLint shaderStringLengths[2] = { strlen( shaderDefine ), (GLint)shaderContentsLength };
GLuint shader = glCreateShader( shaderType );
glShaderSource( shader, 2, shaderStrings, shaderStringLengths );
glCompileShader( shader );
GLint isCompiled;
glGetShaderiv( shader, GL_COMPILE_STATUS, &isCompiled );
if( isCompiled == GL_FALSE )
{
GLint maxLength;
glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &maxLength );
char *log = (char*)malloc( maxLength );
glGetShaderInfoLog( shader, maxLength, &maxLength, log );
printf( "Error in shader: %s\n%s\n%s\n", log, shaderStrings[0], shaderStrings[1] );
exit( 1 );
}
return shader;
}
static GLuint shader_load( const char *shaderContents )
{
GLuint result;
GLuint vert = shader_compile( shaderContents, strlen( shaderContents ), GL_VERTEX_SHADER );
GLuint frag = shader_compile( shaderContents, strlen( shaderContents ), GL_FRAGMENT_SHADER );
GLuint ref = glCreateProgram();
glAttachShader( ref, vert );
glAttachShader( ref, frag );
glLinkProgram ( ref );
glDetachShader( ref, vert );
glDetachShader( ref, frag );
result = ref;
return result;
}
static void load_collision_mesh( CollisionMesh *mesh )
{
mesh->num_vertices = 3 * surfaces_count;
mesh->position = malloc( sizeof( float ) * surfaces_count * 9 );
mesh->normal = malloc( sizeof( float ) * surfaces_count * 9 );
mesh->index = malloc( sizeof( uint16_t ) * surfaces_count * 3 );
for( size_t i = 0; i < surfaces_count; ++i )
{
const struct SM64Surface *surf = &surfaces[i];
float x1 = mesh->position[9*i+0] = surf->vertices[0][0];
float y1 = mesh->position[9*i+1] = surf->vertices[0][1];
float z1 = mesh->position[9*i+2] = surf->vertices[0][2];
float x2 = mesh->position[9*i+3] = surf->vertices[1][0];
float y2 = mesh->position[9*i+4] = surf->vertices[1][1];
float z2 = mesh->position[9*i+5] = surf->vertices[1][2];
float x3 = mesh->position[9*i+6] = surf->vertices[2][0];
float y3 = mesh->position[9*i+7] = surf->vertices[2][1];
float z3 = mesh->position[9*i+8] = surf->vertices[2][2];
float nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2);
float ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2);
float nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2);
float mag = sqrtf(nx * nx + ny * ny + nz * nz);
nx /= mag;
ny /= mag;
nz /= mag;
mesh->normal[9*i+0] = nx;
mesh->normal[9*i+1] = ny;
mesh->normal[9*i+2] = nz;
mesh->normal[9*i+3] = nx;
mesh->normal[9*i+4] = ny;
mesh->normal[9*i+5] = nz;
mesh->normal[9*i+6] = nx;
mesh->normal[9*i+7] = ny;
mesh->normal[9*i+8] = nz;
mesh->index[3*i+0] = 3*i+0;
mesh->index[3*i+1] = 3*i+1;
mesh->index[3*i+2] = 3*i+2;
}
glGenVertexArrays( 1, &mesh->vao );
glBindVertexArray( mesh->vao );
#define X( loc, buff, arr, type ) do { \
glGenBuffers( 1, &buff ); \
glBindBuffer( GL_ARRAY_BUFFER, buff ); \
glBufferData( GL_ARRAY_BUFFER, mesh->num_vertices*sizeof( type ), arr, GL_STATIC_DRAW ); \
glEnableVertexAttribArray( loc ); \
glVertexAttribPointer( loc, sizeof( type ) / sizeof( float ), GL_FLOAT, GL_FALSE, sizeof( type ), NULL ); \
} while( 0 )
X( 0, mesh->position_buffer, mesh->position, vec3 );
X( 1, mesh->normal_buffer, mesh->normal, vec3 );
#undef X
}
static void load_mario_mesh( MarioMesh *mesh, struct SM64MarioGeometryBuffers *marioGeo )
{
mesh->index = malloc( 3 * SM64_GEO_MAX_TRIANGLES * sizeof(uint16_t) );
for( int i = 0; i < 3 * SM64_GEO_MAX_TRIANGLES; ++i )
mesh->index[i] = i;
mesh->num_vertices = 3 * SM64_GEO_MAX_TRIANGLES;
glGenVertexArrays( 1, &mesh->vao );
glBindVertexArray( mesh->vao );
#define X( loc, buff, arr, type ) do { \
glGenBuffers( 1, &buff ); \
glBindBuffer( GL_ARRAY_BUFFER, buff ); \
glBufferData( GL_ARRAY_BUFFER, sizeof( type ) * 3 * SM64_GEO_MAX_TRIANGLES, arr, GL_DYNAMIC_DRAW ); \
glEnableVertexAttribArray( loc ); \
glVertexAttribPointer( loc, sizeof( type ) / sizeof( float ), GL_FLOAT, GL_FALSE, sizeof( type ), NULL ); \
} while( 0 )
X( 0, mesh->position_buffer, marioGeo->position, vec3 );
X( 1, mesh->normal_buffer, marioGeo->normal, vec3 );
X( 2, mesh->color_buffer, marioGeo->color, vec3 );
X( 3, mesh->uv_buffer, marioGeo->uv, vec2 );
#undef X
}
static void update_mario_mesh( MarioMesh *mesh, struct SM64MarioGeometryBuffers *marioGeo )
{
if( mesh->index == NULL )
load_mario_mesh( mesh, marioGeo );
mesh->num_vertices = 3 * marioGeo->numTrianglesUsed;
glBindBuffer( GL_ARRAY_BUFFER, mesh->position_buffer );
glBufferData( GL_ARRAY_BUFFER, sizeof( vec3 ) * 3 * SM64_GEO_MAX_TRIANGLES, marioGeo->position, GL_DYNAMIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, mesh->normal_buffer );
glBufferData( GL_ARRAY_BUFFER, sizeof( vec3 ) * 3 * SM64_GEO_MAX_TRIANGLES, marioGeo->normal, GL_DYNAMIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, mesh->color_buffer );
glBufferData( GL_ARRAY_BUFFER, sizeof( vec3 ) * 3 * SM64_GEO_MAX_TRIANGLES, marioGeo->color, GL_DYNAMIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, mesh->uv_buffer );
glBufferData( GL_ARRAY_BUFFER, sizeof( vec2 ) * 3 * SM64_GEO_MAX_TRIANGLES, marioGeo->uv, GL_DYNAMIC_DRAW );
}
void render_state_init( RenderState *renderState, uint8_t *marioTexture )
{
load_collision_mesh( &renderState->collision );
renderState->world_shader = shader_load( WORLD_SHADER );
renderState->mario_shader = shader_load( MARIO_SHADER );
glEnable( GL_CULL_FACE );
glCullFace( GL_BACK );
glClearColor( 0.2f, 0.2f, 0.2f, 1.0f );
glEnable( GL_DEPTH_TEST );
glGenTextures( 1, &renderState->mario_texture );
glBindTexture( GL_TEXTURE_2D, renderState->mario_texture );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SM64_TEXTURE_WIDTH, SM64_TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, marioTexture);
}
void render_draw( RenderState *renderState, const vec3 camPos, const struct SM64MarioState *marioState, struct SM64MarioGeometryBuffers *marioGeo )
{
update_mario_mesh( &renderState->mario, marioGeo );
mat4 model, view, projection;
glm_perspective( 45.0f, (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 100.0f, 20000.0f, projection );
glm_translate( view, (float*)camPos );
glm_lookat( (float*)camPos, (float*)marioState->position, (vec3){0,1,0}, view );
glm_mat4_identity( model );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glUseProgram( renderState->world_shader );
glBindVertexArray( renderState->collision.vao );
glUniformMatrix4fv( glGetUniformLocation( renderState->world_shader, "model" ), 1, GL_FALSE, (GLfloat*)model );
glUniformMatrix4fv( glGetUniformLocation( renderState->world_shader, "view" ), 1, GL_FALSE, (GLfloat*)view );
glUniformMatrix4fv( glGetUniformLocation( renderState->world_shader, "projection" ), 1, GL_FALSE, (GLfloat*)projection );
glDrawElements( GL_TRIANGLES, renderState->collision.num_vertices, GL_UNSIGNED_SHORT, renderState->collision.index );
glUseProgram( renderState->mario_shader );
glActiveTexture( GL_TEXTURE0 );
glBindTexture( GL_TEXTURE_2D, renderState->mario_texture );
glBindVertexArray( renderState->mario.vao );
glUniformMatrix4fv( glGetUniformLocation( renderState->mario_shader, "view" ), 1, GL_FALSE, (GLfloat*)view );
glUniformMatrix4fv( glGetUniformLocation( renderState->mario_shader, "projection" ), 1, GL_FALSE, (GLfloat*)projection );
glUniform1i( glGetUniformLocation( renderState->mario_shader, "marioTex" ), 0 );
glDrawElements( GL_TRIANGLES, renderState->mario.num_vertices, GL_UNSIGNED_SHORT, renderState->mario.index );
}
static float read_axis( int16_t val )
{
float result = (float)val / 32767.0f;
if( result < 0.2f && result > -0.2f )
return 0.0f;
return result > 0.0f ? (result - 0.2f) / 0.8f : (result + 0.2f) / 0.8f;
}
int main( void )
{
size_t romSize;
uint8_t *rom = utils_read_file_alloc( "baserom.us.z64", &romSize );
uint8_t *texture = malloc( 4 * SM64_TEXTURE_WIDTH * SM64_TEXTURE_HEIGHT );
sm64_global_init( rom, texture, NULL );
sm64_load_surfaces( 0, surfaces, surfaces_count );
sm64_mario_reset( 0, 1000, 0 );
free( rom );
RenderState renderState;
renderState.mario.index = NULL;
vec3 cameraPos = { 0, 0, 0 };
float cameraRot = 0.0f;
context_init( "libsm64", WINDOW_WIDTH, WINDOW_HEIGHT );
render_state_init( &renderState, texture );
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 0;
struct SM64MarioInputs marioInputs;
struct SM64MarioState marioState;
struct SM64MarioGeometryBuffers marioGeometry;
marioGeometry.position = malloc( sizeof(float) * 9 * SM64_GEO_MAX_TRIANGLES );
marioGeometry.color = malloc( sizeof(float) * 9 * SM64_GEO_MAX_TRIANGLES );
marioGeometry.normal = malloc( sizeof(float) * 9 * SM64_GEO_MAX_TRIANGLES );
marioGeometry.uv = malloc( sizeof(float) * 6 * SM64_GEO_MAX_TRIANGLES );
do
{
uint64_t frameTopTime = ns_clock();
SDL_GameController *controller = context_get_controller();
float x_axis = read_axis( SDL_GameControllerGetAxis( controller, SDL_CONTROLLER_AXIS_LEFTX ));
float y_axis = read_axis( SDL_GameControllerGetAxis( controller, SDL_CONTROLLER_AXIS_LEFTY ));
float x0_axis = read_axis( SDL_GameControllerGetAxis( controller, SDL_CONTROLLER_AXIS_RIGHTX ));
cameraRot += 0.1f * x0_axis;
cameraPos[0] = marioState.position[0] + 1000.0f * cosf( cameraRot );
cameraPos[1] = marioState.position[1] + 200.0f;
cameraPos[2] = marioState.position[2] + 1000.0f * sinf( cameraRot );
marioInputs.buttonA = SDL_GameControllerGetButton( controller, 0 );
marioInputs.buttonB = SDL_GameControllerGetButton( controller, 2 );
marioInputs.buttonZ = SDL_GameControllerGetButton( controller, 9 );
marioInputs.camLookX = marioState.position[0] - cameraPos[0];
marioInputs.camLookZ = marioState.position[2] - cameraPos[2];
marioInputs.stickX = x_axis;
marioInputs.stickY = y_axis;
sm64_mario_tick( &marioInputs, &marioState, &marioGeometry );
render_draw( &renderState, cameraPos, &marioState, &marioGeometry );
ts.tv_nsec = 33333333 - (ns_clock() - frameTopTime);
nanosleep( &ts, &ts );
}
while( context_flip_frame_poll_events() );
sm64_global_terminate();
context_terminate();
return 0;
}
+60
View File
@@ -0,0 +1,60 @@
#pragma once
//
// From: http://roxlu.com/2014/047/high-resolution-timer-function-in-c-c--
/* ----------------------------------------------------------------------- */
/*
Easy embeddable cross-platform high resolution timer function. For each
platform we select the high resolution timer. You can call the 'ns()'
function in your file after embedding this.
*/
#include <stdint.h>
#if defined(__linux)
# define HAVE_POSIX_TIMER
# include <time.h>
# ifdef CLOCK_MONOTONIC
# define CLOCKID CLOCK_MONOTONIC
# else
# define CLOCKID CLOCK_REALTIME
# endif
#elif defined(__APPLE__)
# define HAVE_MACH_TIMER
# include <mach/mach_time.h>
#elif defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
static uint64_t ns_clock() {
static uint64_t is_init = 0;
#if defined(__APPLE__)
static mach_timebase_info_data_t info;
if (0 == is_init) {
mach_timebase_info(&info);
is_init = 1;
}
uint64_t now;
now = mach_absolute_time();
now *= info.numer;
now /= info.denom;
return now;
#elif defined(__linux)
static struct timespec linux_rate;
if (0 == is_init) {
clock_getres(CLOCKID, &linux_rate);
is_init = 1;
}
uint64_t now;
struct timespec spec;
clock_gettime(CLOCKID, &spec);
now = spec.tv_sec * 1.0e9 + spec.tv_nsec;
return now;
#elif defined(_WIN32)
static LARGE_INTEGER win_frequency;
if (0 == is_init) {
QueryPerformanceFrequency(&win_frequency);
is_init = 1;
}
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return (uint64_t) ((1e9 * now.QuadPart) / win_frequency.QuadPart);
#endif
}