diff --git a/.gitignore b/.gitignore index 8e1db0a..12fbd57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /src/mario /build/ /dist/ +/test/level.c +/test/level.h *.z64 *.so \ No newline at end of file diff --git a/Makefile b/Makefile index 5325f54..b2be1fe 100644 --- a/Makefile +++ b/Makefile @@ -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) \ No newline at end of file diff --git a/import-test-collision.py b/import-test-collision.py new file mode 100755 index 0000000..fcb5367 --- /dev/null +++ b/import-test-collision.py @@ -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() \ No newline at end of file diff --git a/run-test b/run-test new file mode 100755 index 0000000..b345cbe Binary files /dev/null and b/run-test differ diff --git a/src/game/mario.c b/src/game/mario.c index 44dfcd5..e857623 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -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; diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index bf247e7..7ba9b03 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -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) { diff --git a/src/game/mario_misc.c b/src/game/mario_misc.c index 09441a2..131d9fa 100644 --- a/src/game/mario_misc.c +++ b/src/game/mario_misc.c @@ -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; } diff --git a/src/game/object_stuff.c b/src/game/object_stuff.c index 000c802..7da27e0 100644 --- a/src/game/object_stuff.c +++ b/src/game/object_stuff.c @@ -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); diff --git a/src/load_tex_data.h b/src/load_tex_data.h index f53521a..e08fdcf 100644 --- a/src/load_tex_data.h +++ b/src/load_tex_data.h @@ -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 ); \ No newline at end of file diff --git a/test/cglm.h b/test/cglm.h new file mode 100644 index 0000000..6bd11c9 --- /dev/null +++ b/test/cglm.h @@ -0,0 +1,161 @@ +///////////////////////////////////////////////////////////////////// +// A few choice functions and types from the cglm library +// https://github.com/recp/cglm +// +#pragma once + +#include +#include + +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; +} \ No newline at end of file diff --git a/test/context.c b/test/context.c new file mode 100644 index 0000000..9b5f6c1 --- /dev/null +++ b/test/context.c @@ -0,0 +1,115 @@ +#include "context.h" + +#include + +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; +} \ No newline at end of file diff --git a/test/context.h b/test/context.h new file mode 100644 index 0000000..fc7ea51 --- /dev/null +++ b/test/context.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef __APPLE__ +# include +# include +#elif _WIN32 +# define HAVE_M_PI +# include +# include +#else +# include +# include +#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 ); \ No newline at end of file diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..732cd78 --- /dev/null +++ b/test/main.c @@ -0,0 +1,450 @@ +#define _CRT_SECURE_NO_WARNINGS 1 // for fopen + +#include +#include +#include +#include +#include +#include + +#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; +} \ No newline at end of file diff --git a/test/ns_clock.h b/test/ns_clock.h new file mode 100644 index 0000000..50ec627 --- /dev/null +++ b/test/ns_clock.h @@ -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 +#if defined(__linux) +# define HAVE_POSIX_TIMER +# include +# ifdef CLOCK_MONOTONIC +# define CLOCKID CLOCK_MONOTONIC +# else +# define CLOCKID CLOCK_REALTIME +# endif +#elif defined(__APPLE__) +# define HAVE_MACH_TIMER +# include +#elif defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +#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 +} \ No newline at end of file