#include "raylib.h" #include "rcamera.h" #include "raymath.h" #include "stdbool.h" #include "stddef.h" #include "stdlib.h" #include "stdint.h" #include "rlgl.h" #include "float.h" //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- // Movement constants #define GRAVITY 32.0f #define MAX_SPEED 20.0f #define CROUCH_SPEED 5.0f #define JUMP_FORCE 12.0f #define MAX_ACCEL 150.0f // Grounded drag #define FRICTION 0.86f // Increasing air drag, increases strafing speed #define AIR_DRAG 0.98f // Responsiveness for turning movement direction to looked direction #define CONTROL 15.0f #define CROUCH_HEIGHT 0.0f #define STAND_HEIGHT 2.0f #define BOTTOM_HEIGHT 0.5f // Additional definitions #define NORMALIZE_INPUT 0 #define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) #define FLOOR_EXTENT 25 #define TILE_SIZE 5.0f #define PLAYER_RADIUS 0.5f #define PLAYER_HEIGHT 1.8f enum game_state { STATE_MENU, // 0 by default STATE_GAME, // 1 }; //---------------------------------------------------------------------------------- // RNG //---------------------------------------------------------------------------------- // One-time world seed (set in main) static uint32_t gWorldSeed = 123456789u; // Fast 2D int hash → stable pseudo-random per cell static inline uint32_t hash2i(int x, int z, uint32_t seed) { uint32_t h = (uint32_t)x * 374761393u ^ (uint32_t)z * 668265263u ^ seed * 2654435761u; h ^= h >> 13; h *= 1274126177u; h ^= h >> 16; return h; } // 1-in-N chance using a hashed value static inline bool one_in_n(uint32_t h, int n) { return (h % (uint32_t)n) == 0u; } // Map a few hash bits to [-0.5, +0.5] for jitter static inline float hash01_to_m05p05(uint32_t h_bits) { return ((h_bits & 0xFFu) / 255.0f) - 0.5f; } //---------------------------------------------------------------------------------- //Defining program structure //---------------------------------------------------------------------------------- typedef struct { Camera camera; // <- now part of the game state Model model; // you can keep model & its position here too Vector3 position; Vector3 velocity; Vector3 dir; Vector2 sensitivity; Vector2 lookRotation; Vector2 lean; float headTimer; float walkLerp; float headLerp; bool isGrounded; int cameraMode; } Player; typedef struct { int index; enum game_state state; int shouldExit; Player player; } Master; //------------------------------------------------------------------------------------ // Function Initation //------------------------------------------------------------------------------------ void Simple_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, int screenWidth, int screenHeight, Color color); void Thick_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, float line_width, int screenWidth, int screenHeight, Color color); void Simple_Menu(const char *array[], int count, int index, int x1, int y1, int total_width, int total_height, int line_thickess, int font_size, Color color, Color alt_color); void UpdateBody(Master* g, float rot, char side, char forward, bool jumpPressed, bool crouchHold); static void UpdateCameraAngle(Master* g); static void DrawLevel(Model mdlTile); static void CenterMouseAndFlush(void); int Random(int x); static void RenderTrees(Master* g, Texture2D tree, Vector3 treepos, float treeSize); static void ApplyFogShaderToModel(Model *m, Shader fog); static BoundingBox MakeModelWorldAABB(Model m, Vector3 pos, Vector3 scale, float yawDeg); static BoundingBox PlayerAABB(Vector3 p); static void ResolveAgainstBoxXZ(Master* g, BoundingBox box); static float ModelLengthXUnits(Model m); static float ModelLengthZUnits(Model m); static void DrawPerimeter(Model wall, float border, float scale); static void CollidePerimeter(Master* g, Model wall, float border, float scale); // optional //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ int main(void) { // Initialization //-------------------------------------------------------------------------------------- //Define window size, create window, establish fps target, fullscreen. const int screenWidth = 1440*2; const int screenHeight = 960*2; // BEFORE InitWindow (optional but nice): enable MSAA for smoother alpha edges SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib"); SetTargetFPS(60); // Set our game to run at 60 frames-per-second ToggleFullscreen(); CenterMouseAndFlush(); DisableCursor(); // Limit cursor to relative movement inside the window //Define menu characteristics const char *menu[] = {"Game", "Option 2", "Exit"}; int index_length = ARRLEN(menu); //General game state initiation Master g = (Master){0}; g.index = 0; g.state = STATE_MENU; g.shouldExit = 0; //Define player values g.player.sensitivity = (Vector2){ 0.001f, 0.001f }; g.player.lookRotation = (Vector2){ 0 }; g.player.headTimer = 0.0f; g.player.walkLerp = 0.0f; g.player.headLerp = STAND_HEIGHT; g.player.lean = (Vector2){ 0 }; //Define the player camera g.player.camera = (Camera){0}; g.player.camera.position = (Vector3){ g.player.position.x, g.player.position.y + (BOTTOM_HEIGHT + g.player.headLerp), g.player.position.z, }; g.player.camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; g.player.camera.fovy = 60.0f; g.player.camera.projection = CAMERA_PERSPECTIVE; g.player.cameraMode = CAMERA_FIRST_PERSON; UpdateCameraAngle(&g); // Load GLTF model (no animations) //Model wall = LoadModel("resources/3d_models/Tiny_Treats_Homely_House_1.0_FREE/Assets/gltf/fence_straight.gltf"); Model wall = LoadModel("resources/3d_models/RuinsGLB/Walls/WallNormalXL.glb"); Vector3 wall_position = { 10.0f, 0.0f, 0.0f }; float wall_yaw = 0.0f; // rotate around Y float wall_pitch = 0.0f; // rotate around X float wall_roll = 0.0f; // rotate around Z Model acorn = LoadModel("resources/3d_models/my_models/my_acorn.glb"); Vector3 acorn_position = { 0.0f, 0.0f, 0.0f }; float acorn_yaw = 0.0f; // rotate around Y float acorn_pitch = 0.0f; // rotate around X float acorn_roll = 0.0f; // rotate around Z // Load a 2D image as texture for the billboard //Billboards always point toward the active camera. Texture2D sign = LoadTexture("resources/sprites/husk/ZOMBC1.png"); //Texture2D sign = LoadTexture("resources/sprites/diard-charles.jpg"); GenTextureMipmaps(&sign); SetTextureFilter(sign, TEXTURE_FILTER_POINT); // optional but looks nicer Vector3 bbPos = (Vector3){ -1.0f, 2.0f, -1.0f }; // where in 3D world to place it float bbSize = 4.0f; // billboard height in world units // Load a 2D image as texture for the billboard //Billboards always point toward the active camera. Texture2D tree = LoadTexture("resources/sprites/tree.png"); GenTextureMipmaps(&tree); SetTextureFilter(tree, TEXTURE_FILTER_BILINEAR); // optional but looks nicer Vector3 treepos = (Vector3){ 0.0f, 0.0f, 0.0f }; // where in 3D world to place it float treeSize = 15.0f; // billboard height in world units float cutoffAlpha = 0.7f; //Needs to be aggressive to remove the alpha. // Grass tile setup Texture2D texGrass = LoadTexture("resources/textures/grass_textures/Grass_01.png"); GenTextureMipmaps(&texGrass); SetTextureFilter(texGrass, TEXTURE_FILTER_TRILINEAR); // nicer at angles SetTextureWrap(texGrass, TEXTURE_WRAP_REPEAT); // A 3D plane (centered at origin) of size tileSize x tileSize const float tileSize = TILE_SIZE; Mesh meshTile = GenMeshPlane(tileSize, tileSize, 1, 1); Model mdlTile = LoadModelFromMesh(meshTile); mdlTile.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = texGrass; // --- Skybox setup --- // Generate cube mesh Mesh cube = GenMeshCube(1.0f, 1.0f, 1.0f); Model skybox = LoadModelFromMesh(cube); // Load skybox shader Shader skyboxShader = LoadShader ( "shaders/skybox.vs", "shaders/skybox.fs" ); // Attach shader to skybox skybox.materials[0].shader = skyboxShader; Image img = LoadImage("resources/cubemap/cubemap.png"); skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(img, CUBEMAP_LAYOUT_AUTO_DETECT); UnloadImage(img); //no longer need image loaded. // Tell the shader which material map is the environmentMap int mapType = MATERIAL_MAP_CUBEMAP; SetShaderValue(skyboxShader, GetShaderLocation(skyboxShader, "environmentMap"), &mapType, SHADER_UNIFORM_INT); //Flip/gamma options (set to 0 for PNG cross) int vflipped = 0; int doGamma = 0; SetShaderValue(skyboxShader, GetShaderLocation(skyboxShader, "vflipped"), &vflipped, SHADER_UNIFORM_INT); SetShaderValue(skyboxShader, GetShaderLocation(skyboxShader, "doGamma"), &doGamma, SHADER_UNIFORM_INT); // Load the fog shader Shader fog = LoadShader("shaders/fog.vs", "shaders/fog.fs"); // Grab uniform locations (cache them) int locPlayer = GetShaderLocation(fog, "u_playerPos"); int locRadius = GetShaderLocation(fog, "u_fogRadius"); int locFeather = GetShaderLocation(fog, "u_fogFeather"); int locFogColor = GetShaderLocation(fog, "u_fogColor"); int locCutoff_fog = GetShaderLocation(fog, "u_alphaCutoff"); int locInner = GetShaderLocation(fog, "u_inner"); int locOuter = GetShaderLocation(fog, "u_outer"); int locMinShadow = GetShaderLocation(fog, "u_minShadow"); // Optional if you added the sharpness curve in the shader int locSharpness = GetShaderLocation(fog, "u_sharpness"); //Flashlight int locSpotDir = GetShaderLocation(fog, "u_spotDir"); int locSpotCos = GetShaderLocation(fog, "u_spotCos"); int locSpotSoft = GetShaderLocation(fog, "u_spotSoft"); int locLightRange = GetShaderLocation(fog, "u_lightRange"); int locLightFade = GetShaderLocation(fog, "u_lightFade"); int locIntensity = GetShaderLocation(fog, "u_intensity"); int locBeamSpread = GetShaderLocation(fog, "u_beamSpread"); // Let raylib know which matrices the shader expects fog.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocation(fog, "matModel"); fog.locs[SHADER_LOC_MATRIX_VIEW] = GetShaderLocation(fog, "matView"); fog.locs[SHADER_LOC_MATRIX_PROJECTION] = GetShaderLocation(fog, "matProjection"); // --- Apply shader to your ground tile model --- mdlTile.materials[0].shader = fog; // same shader also works for opaque //Apply shader to wall ApplyFogShaderToModel(&wall, fog); ApplyFogShaderToModel(&acorn, fog); // Defaults for opaque geometry (no alpha cut) float cutoffOpaque = 0.0f; SetShaderValue(fog, locCutoff_fog, &cutoffOpaque, SHADER_UNIFORM_FLOAT); // Fog/vignette tuning float inner = 0.0f; // start radius (center under player) float outer = 20.0f; // how far until full fog float minShadow = 0.85f; // baseline darkness at the center (0 = none, 1 = pitch black) float sharpness = 2.0f; // steeper edge (optional if shader uses it) Vector4 fogColor = {0.0f, 0.0f, 0.0f, 1.0f}; // Flashligh tuning float spotAngleDeg = 35.0f; //cone angle float spotCos = cosf(DEG2RAD * (spotAngleDeg * 0.5f)); float spotSoft = 0.10f; float lightRange = 150.0f;//100.0f; float lightFade = 1.0f; float intensity = 0.25f; // try 0.7–1.5 to taste float beamSpread = 0.05f; // try 0.15–0.35; higher = less “laser” at distance SetShaderValue(fog, locIntensity, &intensity, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locBeamSpread, &beamSpread, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locSpotCos, &spotCos, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locSpotSoft, &spotSoft, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locLightRange, &lightRange, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locLightFade, &lightFade, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locInner, &inner, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locOuter, &outer, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locMinShadow, &minShadow, SHADER_UNIFORM_FLOAT); SetShaderValue(fog, locFogColor, &fogColor, SHADER_UNIFORM_VEC4); SetShaderValue(fog, locSharpness, &sharpness, SHADER_UNIFORM_FLOAT); //-------------------------------------------------------------------------------------- // Main game loop //-------------------------------------------------------------------------------------- while (!g.shouldExit && !WindowShouldClose()) // Detect window close button or ESC key { // Update //---------------------------------------------------------------------------------- switch (g.state) { case STATE_MENU: { if (IsKeyPressed(KEY_UP) && g.index > 0) { g.index -= 1; break; }; if (IsKeyPressed(KEY_DOWN) && g.index < index_length - 1) { g.index += 1; break; }; if (IsKeyPressed(KEY_ENTER)) { switch (g.index) { case 0: { g.state = 1; CenterMouseAndFlush(); break; } case 1: { break; } case 2: { g.shouldExit = 1; break; } } }; break; }; case STATE_GAME: { Vector2 mouseDelta = GetMouseDelta(); g.player.lookRotation.x -= mouseDelta.x*g.player.sensitivity.x; g.player.lookRotation.y += mouseDelta.y*g.player.sensitivity.y; char sideway = (IsKeyDown(KEY_D) - IsKeyDown(KEY_A)); char forward = (IsKeyDown(KEY_W) - IsKeyDown(KEY_S)); bool crouching = IsKeyDown(KEY_LEFT_CONTROL); UpdateBody(&g, g.player.lookRotation.x, sideway, forward, IsKeyDown(KEY_SPACE), crouching); // Build wall collider (scale must match your DrawModelEx scale) BoundingBox acornBox = MakeModelWorldAABB(acorn, acorn_position, (Vector3){0.25,0.25,0.25}, acorn_yaw); // Collide player vs wall (XZ slide) ResolveAgainstBoxXZ(&g, acornBox); // put near your update logic float B = 122; float T = 1.0f; // wall half-thickness float H = 4.0f; // wall height BoundingBox walls[4] = { // left (x = -B) { (Vector3){-B - T, 0.0f, -B - T}, (Vector3){-B + T, H, B + T} }, // right (x = +B) { (Vector3){ B - T, 0.0f, -B - T}, (Vector3){ B + T, H, B + T} }, // bottom (z = -B) { (Vector3){-B - T, 0.0f, -B - T}, (Vector3){ B + T, H, -B + T} }, // top (z = +B) { (Vector3){-B - T, 0.0f, B - T}, (Vector3){ B + T, H, B + T} }, }; for (int i = 0; i < 4; ++i) ResolveAgainstBoxXZ(&g, walls[i]); // (Optional) debug draw inside BeginMode3D/EndMode3D: for (int i = 0; i < 4; ++i) DrawBoundingBox(walls[i], RED); float delta = GetFrameTime(); g.player.headLerp = Lerp(g.player.headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.0f*delta); g.player.camera.position = (Vector3){ g.player.position.x, g.player.position.y + (BOTTOM_HEIGHT + g.player.headLerp), g.player.position.z, }; if (g.player.isGrounded && ((forward != 0) || (sideway != 0))) { g.player.headTimer += delta*3.0f; g.player.walkLerp = Lerp(g.player.walkLerp, 1.0f, 10.0f*delta); g.player.camera.fovy = Lerp(g.player.camera.fovy, 55.0f, 5.0f*delta); } else { g.player.walkLerp = Lerp(g.player.walkLerp, 0.0f, 10.0f*delta); g.player.camera.fovy = Lerp(g.player.camera.fovy, 60.0f, 5.0f*delta); } g.player.lean.x = Lerp(g.player.lean.x, sideway*0.02f, 10.0f*delta); g.player.lean.y = Lerp(g.player.lean.y, forward*0.015f, 10.0f*delta); bbPos.x += (g.player.position.x - bbPos.x)*.005; bbPos.z += (g.player.position.z - bbPos.z)*.005; // wall editor if (IsKeyDown(KEY_RIGHT) && IsKeyDown(KEY_LEFT_SHIFT)) acorn_position.x += 0.5f; else if (IsKeyPressed(KEY_RIGHT)) acorn_position.x += 0.5f; if (IsKeyDown(KEY_LEFT) && IsKeyDown(KEY_LEFT_SHIFT)) acorn_position.x -= 0.5f; else if (IsKeyPressed(KEY_LEFT)) acorn_position.x -= 0.5f; if (IsKeyDown(KEY_UP) && IsKeyDown(KEY_LEFT_SHIFT)) acorn_position.z += 0.5f; else if (IsKeyPressed(KEY_UP)) acorn_position.z += 0.5f; if (IsKeyDown(KEY_DOWN) && IsKeyDown(KEY_LEFT_SHIFT)) acorn_position.z -= 0.5f; else if (IsKeyPressed(KEY_DOWN)) acorn_position.z -= 0.5f; float rotSpeed = 45.0f * GetFrameTime(); // deg/sec if (IsKeyDown(KEY_Q)) wall_yaw -= rotSpeed; if (IsKeyDown(KEY_E)) wall_yaw += rotSpeed; // UpdateCameraAngle(&g); break; }; } //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); switch (g.state) { case STATE_MENU: { ClearBackground(BLACK); Simple_Menu(menu, ARRLEN(menu), g.index, screenWidth * 0.15, screenHeight * 0.15, 500, screenHeight * 0.7, 10, 50, GREEN, LIGHTGRAY); Thick_Center_Box(0.1, 0.1, 10, screenWidth, screenHeight, GREEN); break; } case STATE_GAME: { ClearBackground(BLACK); Vector3 camPos = g.player.camera.position; Vector3 camFwd = Vector3Normalize(Vector3Subtract(g.player.camera.target, camPos)); // Camera forward (normalize target - position) Vector3 forward = Vector3Normalize(Vector3Subtract(g.player.camera.target, g.player.camera.position)); SetShaderValue(fog, locPlayer, &camPos, SHADER_UNIFORM_VEC3); SetShaderValue(fog, locSpotDir, &camFwd, SHADER_UNIFORM_VEC3); BeginMode3D(g.player.camera); rlDisableBackfaceCulling(); rlDisableDepthMask(); // keep it centered on the camera skybox.transform = MatrixTranslate( g.player.camera.position.x, g.player.camera.position.y, g.player.camera.position.z ); DrawModel(skybox, (Vector3){0}, 1.0f, WHITE); rlEnableDepthMask(); rlEnableBackfaceCulling(); DrawLevel(mdlTile); // --- Trees use same fog shader but with alpha-cut enabled --- SetShaderValue(fog, locCutoff_fog, &cutoffAlpha, SHADER_UNIFORM_FLOAT); BeginShaderMode(fog); //DrawModel(wall, wall_position, 5.0, WHITE); DrawModelEx(acorn, acorn_position, (Vector3){0,1,0}, wall_yaw, (Vector3){0.25,0.25,0.25}, WHITE); RenderTrees(&g, tree, treepos, treeSize); //DrawBillboard(g.player.camera, sign, bbPos, bbSize, WHITE); //zombie DrawPerimeter(wall, 122.0f, 1.5f); EndShaderMode(); // Restore opaque cutoff for anything else you draw with that material later SetShaderValue(fog, locCutoff_fog, &cutoffOpaque, SHADER_UNIFORM_FLOAT); EndMode3D(); // Draw info box DrawRectangle(5, 5, 330*1.5, 75*1.25, Fade(SKYBLUE, 0.5f)); DrawRectangleLines(5, 5, 330*1.5, 75*1.25, BLUE); DrawText("Camera controls:", 15, 15, 20, BLACK); DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl", 15, 30, 20, BLACK); DrawText("- Look around: arrow keys or mouse", 15, 45, 20, BLACK); DrawText(TextFormat("- Velocity Len: (%06.3f)", Vector2Length((Vector2){ g.player.velocity.x, g.player.velocity.z })), 15, 60, 20, BLACK); DrawText( TextFormat("x: (%06.3f), y: (%06.3f), z: (%06.3f)", wall_position.x, wall_position.y, wall_position.z), 15, 75, 20, BLACK ); break; } } EndDrawing(); //---------------------------------------------------------------------------------- }; //-------------------------------------------------------------------------------------- // De-Initialization //-------------------------------------------------------------------------------------- // Detach the texture from the model so UnloadModel won't try to free it. mdlTile.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = (Texture2D){0}; // Now free resources you loaded: UnloadModel(mdlTile); // frees the model and its mesh/VBOs UnloadTexture(texGrass); // frees the grass texture you loaded UnloadTexture(tree); // frees tree texture UnloadTexture(sign); UnloadModel(wall); UnloadModel(acorn); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- return 0; }; //------------------------------------------------------------------------------------ // Function Definitions //------------------------------------------------------------------------------------ //Function creates a simple box outline centered in the screen with a symetric ofset from the screen borders. void Simple_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, int screenWidth, int screenHeight, Color color) { int x1 = percent_horizontal_gap * screenWidth; int y1 = percent_verticle_gap * screenHeight; int w = screenWidth * (1 - (2 * percent_horizontal_gap)); int h = screenHeight * (1 - (2 * percent_verticle_gap)); DrawRectangleLines(x1, y1, w, h, color); } //Function creates a thick walled box outline centered in the screen with a symetric ofset from the screen borders. void Thick_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, float line_width, int screenWidth, int screenHeight, Color color) { int x1 = percent_horizontal_gap * screenWidth; int y1 = percent_verticle_gap * screenHeight; int w = screenWidth * (1 - (2 * percent_horizontal_gap)); int h = screenHeight * (1 - (2 * percent_verticle_gap)); struct Rectangle rec = {x1,y1,w,h}; DrawRectangleLinesEx(rec, line_width, color); } //Make a simple menu with selection features void Simple_Menu(const char *array[], int count, int index, int x1, int y1, int total_width, int total_height, int line_thickess, int font_size, Color color, Color alt_color) { float height = 0.8f * total_height; int space = 0.2 * total_height / (count - 1); struct Rectangle rec = {x1,y1,total_width,(float)height/count}; for (int i = 0; i < count; ++i) { int text_width = MeasureText(array[i], font_size); if(i == index) { DrawRectangleLinesEx(rec, line_thickess, alt_color); DrawText(array[i], rec.x + (((total_width - 2.0 * line_thickess) - text_width)/2), rec.y + (height/count/2) - font_size/2.0f, font_size, alt_color); } else { DrawRectangleLinesEx(rec, line_thickess, color); DrawText(array[i], rec.x + (((total_width - 2.0 * line_thickess) - text_width)/2), rec.y + (height/count/2) - font_size/2.0f, font_size, color); } rec.y = rec.y + (float)height/count + space; } } void UpdateBody(Master* g, float rot, char side, char forward, bool jumpPressed, bool crouchHold) { Vector2 input = (Vector2){ (float)side, (float)-forward }; #if defined(NORMALIZE_INPUT) // Slow down diagonal movement if ((side != 0) && (forward != 0)) input = Vector2Normalize(input); #endif float delta = GetFrameTime(); if (!g->player.isGrounded) g->player.velocity.y -= GRAVITY*delta; if (g->player.isGrounded && jumpPressed) { g->player.velocity.y = JUMP_FORCE; g->player.isGrounded = false; // Sound can be played at this moment //SetSoundPitch(fxJump, 1.0f + (GetRandomValue(-100, 100)*0.001)); //PlaySound(fxJump); } Vector3 front = (Vector3){ sin(rot), 0.f, cos(rot) }; Vector3 right = (Vector3){ cos(-rot), 0.f, sin(-rot) }; Vector3 desiredDir = (Vector3){ input.x*right.x + input.y*front.x, 0.0f, input.x*right.z + input.y*front.z, }; g->player.dir = Vector3Lerp(g->player.dir, desiredDir, CONTROL*delta); float decel = (g->player.isGrounded ? FRICTION : AIR_DRAG); Vector3 hvel = (Vector3){ g->player.velocity.x*decel, 0.0f, g->player.velocity.z*decel }; float hvelLength = Vector3Length(hvel); // Magnitude if (hvelLength < (MAX_SPEED*0.01f)) hvel = (Vector3){ 0 }; // This is what creates strafing float speed = Vector3DotProduct(hvel, g->player.dir); // Whenever the amount of acceleration to add is clamped by the maximum acceleration constant, // a Player can make the speed faster by bringing the direction closer to horizontal velocity angle // More info here: https://youtu.be/v3zT3Z5apaM?t=165 float maxSpeed = (crouchHold? CROUCH_SPEED : MAX_SPEED); float accel = Clamp(maxSpeed - speed, 0.f, MAX_ACCEL*delta); hvel.x += g->player.dir.x*accel; hvel.z += g->player.dir.z*accel; g->player.velocity.x = hvel.x; g->player.velocity.z = hvel.z; g->player.position.x += g->player.velocity.x*delta; g->player.position.y += g->player.velocity.y*delta; g->player.position.z += g->player.velocity.z*delta; // Fancy collision system against the floor if (g->player.position.y <= 0.0f) { g->player.position.y = 0.0f; g->player.velocity.y = 0.0f; g->player.isGrounded = true; // Enable jumping } } // Update camera static void UpdateCameraAngle(Master* g) { const Vector3 up = (Vector3){ 0.0f, 1.0f, 0.0f }; const Vector3 targetOffset = (Vector3){ 0.0f, 0.0f, -1.0f }; // Left and right Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, g->player.lookRotation.x); // Clamp view up float maxAngleUp = Vector3Angle(up, yaw); maxAngleUp -= 0.001f; // Avoid numerical errors if ( -(g->player.lookRotation.y) > maxAngleUp) { g->player.lookRotation.y = -maxAngleUp; } // Clamp view down float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw); maxAngleDown *= -1.0f; // Downwards angle is negative maxAngleDown += 0.001f; // Avoid numerical errors if ( -(g->player.lookRotation.y) < maxAngleDown) { g->player.lookRotation.y = -maxAngleDown; } // Up and down Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); // Rotate view vector around right axis float pitchAngle = -g->player.lookRotation.y - g->player.lean.y; pitchAngle = Clamp(pitchAngle, -PI / 2 + 0.0001f, PI / 2 - 0.0001f); // Clamp angle so it doesn't go past straight up or straight down Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, pitchAngle); // Head animation // Rotate up direction around forward axis float headSin = sin(g->player.headTimer*PI); float headCos = cos(g->player.headTimer*PI); const float stepRotation = 0.005f; g->player.camera.up = Vector3RotateByAxisAngle(up, pitch, headSin*stepRotation + g->player.lean.x); // Camera BOB const float bobSide = 0.01f; const float bobUp = 0.01f; Vector3 bobbing = Vector3Scale(right, headSin*bobSide); bobbing.y = fabsf(headCos*bobUp); g->player.camera.position = Vector3Add(g->player.camera.position, Vector3Scale(bobbing, g->player.walkLerp)); g->player.camera.target = Vector3Add(g->player.camera.position, pitch); } // Draw game level static void DrawLevel(Model mdlTile) { const int floorExtent = FLOOR_EXTENT; const float tileSize = TILE_SIZE; const Color tileColor1 = (Color){ 150, 200, 200, 255 }; // Floor tiles for (int y = -floorExtent; y < floorExtent; y++) { for (int x = -floorExtent; x < floorExtent; x++) { /* if ((y & 1) && (x & 1)) { DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, tileColor1); } else if (!(y & 1) && !(x & 1)) { DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, LIGHTGRAY); } */ Vector3 pos = (Vector3){ x*tileSize, 0.0f, y*tileSize }; // mdlTile must be accessible here: make it global/static or pass it in DrawModel(mdlTile, pos, 1.0f, WHITE); } } /* const Vector3 towerSize = (Vector3){ 16.0f, 32.0f, 16.0f }; const Color towerColor = (Color){ 150, 200, 200, 255 }; Vector3 towerPos = (Vector3){ 16.0f, 16.0f, 16.0f }; DrawCubeV(towerPos, towerSize, towerColor); DrawCubeWiresV(towerPos, towerSize, DARKBLUE); towerPos.x *= -1; DrawCubeV(towerPos, towerSize, towerColor); DrawCubeWiresV(towerPos, towerSize, DARKBLUE); towerPos.z *= -1; DrawCubeV(towerPos, towerSize, towerColor); DrawCubeWiresV(towerPos, towerSize, DARKBLUE); towerPos.x *= -1; DrawCubeV(towerPos, towerSize, towerColor); DrawCubeWiresV(towerPos, towerSize, DARKBLUE); // Red sun DrawSphere((Vector3){ 300.0f, 300.0f, 0.0f }, 100.0f, (Color){ 255, 0, 0, 255 }); */ } //fix mouse static void CenterMouseAndFlush() { SetMousePosition(GetRenderWidth()/2, GetRenderHeight()/2); (void)GetMouseDelta(); } //Random number gen int Random(int x) { int r = (rand() % x) + 1; // 1–40 return r; } static inline void DrawTreeBillboardStable(Camera cam, Texture2D tex, Vector3 groundPos, float heightWorld, Color tint) { // Keep sprite aspect ratio float aspect = (float)tex.width / (float)tex.height; Vector2 size = (Vector2){ heightWorld * aspect, heightWorld }; // Bottom-center pivot so base sits on groundPos.y Vector2 origin = (Vector2){ 0.5f, 0.75f }; // Fixed world up (ignore camera.up roll) Vector3 up = (Vector3){ 0, 1, 0 }; Rectangle src = (Rectangle){ 0, 0, (float)tex.width, (float)tex.height }; DrawBillboardPro(cam, tex, src, groundPos, up, size, origin, 0.0f, tint); } static void RenderTrees(Master* g, Texture2D tree, Vector3 baseTreePos, float treeSize) { const int floorExtent = FLOOR_EXTENT - 3; const float tileSize = TILE_SIZE; // Placement knobs const int densityA = 10; // 1-in-10 on (odd,odd) const int densityB = 13; // 1-in-14 on (even,even) const float jitter = tileSize * 0.35f; // random in-tile offset const float groundY = baseTreePos.y; // <-- use this as terrain height for (int z = -floorExtent; z < floorExtent; ++z) for (int x = -floorExtent; x < floorExtent; ++x) { uint32_t h = hash2i(x, z, gWorldSeed); uint32_t hA = h; uint32_t hB = h * 0x9E3779B1u + 0x85EBCA6Bu; // (odd,odd) if ((z & 1) && (x & 1) && one_in_n(hA, densityA)) { float ox = hash01_to_m05p05(hA) * jitter; float oz = hash01_to_m05p05(hA >> 8) * jitter; Vector3 pos = { x*tileSize + ox, groundY, z*tileSize + oz }; DrawTreeBillboardStable(g->player.camera, tree, pos, /*height*/ treeSize, WHITE); } // (even,even) if (!(z & 1) && !(x & 1) && one_in_n(hB, densityB)) { float ox = hash01_to_m05p05(hB) * jitter; float oz = hash01_to_m05p05(hB >> 8) * jitter; Vector3 pos = { x*tileSize + ox, groundY, z*tileSize + oz }; DrawTreeBillboardStable(g->player.camera, tree, pos, /*height*/ treeSize, WHITE); } } } static void ApplyFogShaderToModel(Model *m, Shader fog) { // 1) Tell raylib which uniform in your shader is the ALBEDO map fog.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocation(fog, "matModel"); fog.locs[SHADER_LOC_MATRIX_VIEW] = GetShaderLocation(fog, "matView"); fog.locs[SHADER_LOC_MATRIX_PROJECTION] = GetShaderLocation(fog, "matProjection"); fog.locs[SHADER_LOC_MAP_ALBEDO] = GetShaderLocation(fog, "texture0"); // your FS: uniform sampler2D texture0; // (optional but nice) fog.locs[SHADER_LOC_COLOR_DIFFUSE] = GetShaderLocation(fog, "colDiffuse"); // 2) For each material, make sure the base color texture ends up in the ALBEDO slot for (int i = 0; i < m->materialCount; ++i) { Material *mat = &m->materials[i]; // Some importers put the base map in DIFFUSE, copy to ALBEDO if ALBEDO is empty. if (mat->maps[MATERIAL_MAP_ALBEDO].texture.id == 0 && mat->maps[MATERIAL_MAP_DIFFUSE].texture.id != 0) { mat->maps[MATERIAL_MAP_ALBEDO].texture = mat->maps[MATERIAL_MAP_DIFFUSE].texture; } // Ensure non-zero tint if (mat->maps[MATERIAL_MAP_ALBEDO].color.a == 0) mat->maps[MATERIAL_MAP_ALBEDO].color = WHITE; // 3) Attach your fog shader to this material mat->shader = fog; } } // Build player's AABB from its position (XZ radius, fixed height) static BoundingBox PlayerAABB(Vector3 p) { BoundingBox bb; bb.min = (Vector3){ p.x - PLAYER_RADIUS, p.y, p.z - PLAYER_RADIUS }; bb.max = (Vector3){ p.x + PLAYER_RADIUS, p.y + PLAYER_HEIGHT, p.z + PLAYER_RADIUS }; return bb; } // Combine all mesh AABBs of a Model in LOCAL space static BoundingBox ModelLocalAABB(Model m) { BoundingBox out; out.min = (Vector3){ FLT_MAX, FLT_MAX, FLT_MAX }; out.max = (Vector3){ -FLT_MAX, -FLT_MAX, -FLT_MAX }; for (int i = 0; i < m.meshCount; ++i) { BoundingBox b = GetMeshBoundingBox(m.meshes[i]); // local-space box of this mesh if (b.min.x < out.min.x) out.min.x = b.min.x; if (b.min.y < out.min.y) out.min.y = b.min.y; if (b.min.z < out.min.z) out.min.z = b.min.z; if (b.max.x > out.max.x) out.max.x = b.max.x; if (b.max.y > out.max.y) out.max.y = b.max.y; if (b.max.z > out.max.z) out.max.z = b.max.z; } return out; } // Transform a BoundingBox by a matrix and return its world AABB static BoundingBox TransformAABB(BoundingBox bb, Matrix M) { Vector3 c[8] = { {bb.min.x, bb.min.y, bb.min.z}, {bb.max.x, bb.min.y, bb.min.z}, {bb.min.x, bb.max.y, bb.min.z}, {bb.max.x, bb.max.y, bb.min.z}, {bb.min.x, bb.min.y, bb.max.z}, {bb.max.x, bb.min.y, bb.max.z}, {bb.min.x, bb.max.y, bb.max.z}, {bb.max.x, bb.max.y, bb.max.z}, }; BoundingBox out; out.min = (Vector3){ FLT_MAX, FLT_MAX, FLT_MAX }; out.max = (Vector3){ -FLT_MAX, -FLT_MAX, -FLT_MAX }; for (int i = 0; i < 8; ++i) { Vector3 t = Vector3Transform(c[i], M); if (t.x < out.min.x) out.min.x = t.x; if (t.y < out.min.y) out.min.y = t.y; if (t.z < out.min.z) out.min.z = t.z; if (t.x > out.max.x) out.max.x = t.x; if (t.y > out.max.y) out.max.y = t.y; if (t.z > out.max.z) out.max.z = t.z; } return out; } // Build world AABB for a model using the same T*R*S you render with static BoundingBox MakeModelWorldAABB(Model m, Vector3 pos, Vector3 scale, float yawDeg) { Matrix S = MatrixScale(scale.x, scale.y, scale.z); Matrix R = MatrixRotateY(DEG2RAD * yawDeg); Matrix T = MatrixTranslate(pos.x, pos.y, pos.z); Matrix SRT = MatrixMultiply(MatrixMultiply(S, R), T); // S * R * T Matrix M = MatrixMultiply(m.transform, SRT); // model.transform * SRT BoundingBox out = { .min = { FLT_MAX, FLT_MAX, FLT_MAX }, .max = { -FLT_MAX, -FLT_MAX, -FLT_MAX } }; for (int i = 0; i < m.meshCount; ++i) { BoundingBox local = GetMeshBoundingBox(m.meshes[i]); BoundingBox world = TransformAABB(local, M); if (world.min.x < out.min.x) out.min.x = world.min.x; if (world.min.y < out.min.y) out.min.y = world.min.y; if (world.min.z < out.min.z) out.min.z = world.min.z; if (world.max.x > out.max.x) out.max.x = world.max.x; if (world.max.y > out.max.y) out.max.y = world.max.y; if (world.max.z > out.max.z) out.max.z = world.max.z; } return out; } // Push player out of a box on the smallest X/Z overlap (slide) and zero that velocity axis static void ResolveAgainstBoxXZ(Master* g, BoundingBox box) { BoundingBox p = PlayerAABB(g->player.position); // Quick reject if (!CheckCollisionBoxes(p, box)) return; // Overlaps on X and Z float ox = fminf(p.max.x, box.max.x) - fmaxf(p.min.x, box.min.x); float oz = fminf(p.max.z, box.max.z) - fmaxf(p.min.z, box.min.z); if (ox <= 0 || oz <= 0) return; // Centers to know which way to push float cxp = 0.5f * (p.min.x + p.max.x); float czp = 0.5f * (p.min.z + p.max.z); float cxb = 0.5f * (box.min.x + box.max.x); float czb = 0.5f * (box.min.z + box.max.z); // Resolve along the smallest axis if (ox < oz) { float push = (cxp < cxb) ? -ox : ox; g->player.position.x += push; g->player.velocity.x = 0.0f; } else { float push = (czp < czb) ? -oz : oz; g->player.position.z += push; g->player.velocity.z = 0.0f; } } // Length of the model along local X (used to tile segments cleanly) static float ModelLengthXUnits(Model m) { BoundingBox bb = ModelLocalAABB(m); // you already have this helper return (bb.max.x - bb.min.x); } static float ModelLengthZUnits(Model m) { BoundingBox bb = ModelLocalAABB(m); return (bb.max.z - bb.min.z); } // Draw repeated wall segments around the perimeter (z=±border, x=±border) static void DrawPerimeter(Model wall, float border, float scale) { const float HALF = FLOOR_EXTENT * TILE_SIZE; // e.g. 25*5 = 125 Vector3 scl = { scale, scale, scale }; //float segLen = ModelLengthXUnits(wall) * scale; float segLen = ModelLengthZUnits(wall) * scale; if (segLen <= 0.001f) segLen = TILE_SIZE; // safety fallback int count = (int)ceilf((2.0f * HALF) / segLen) + 1; scl.x += 0.1; scl.y += 0.1; scl.z += 0.1; // Top & bottom edges: fence runs along +X (yaw 0/180) for (int i = 0; i < count; ++i) { float x = -HALF + i * segLen; DrawModelEx(wall, (Vector3){ x, 0.0f, border }, (Vector3){0,1,0}, 90.0f, scl, WHITE); DrawModelEx(wall, (Vector3){ x, 0.0f, -border }, (Vector3){0,1,0}, 270.0f, scl, WHITE); } // Left & right edges: rotate 90° so fence runs along Z for (int i = 0; i < count; ++i) { float z = -HALF + i * segLen; DrawModelEx(wall, (Vector3){ border, 0.0f, z }, (Vector3){0,1,0}, 0.0f, scl, WHITE); DrawModelEx(wall, (Vector3){ -border, 0.0f, z }, (Vector3){0,1,0}, 180.0f, scl, WHITE); } } // Optional: collide player with the same perimeter using your AABB helpers static void CollidePerimeter(Master* g, Model wall, float border, float scale) { const float HALF = FLOOR_EXTENT * TILE_SIZE; const Vector3 scl = { scale, scale, scale }; float segLen = ModelLengthXUnits(wall) * scale; if (segLen <= 0.001f) segLen = TILE_SIZE; int count = (int)ceilf((2.0f * HALF) / segLen) + 1; for (int i = 0; i < count; ++i) { float x = -HALF + i * segLen; ResolveAgainstBoxXZ(g, MakeModelWorldAABB(wall, (Vector3){ x, 0.0f, border }, scl, 0.0f)); ResolveAgainstBoxXZ(g, MakeModelWorldAABB(wall, (Vector3){ x, 0.0f, -border }, scl, 180.0f)); } for (int i = 0; i < count; ++i) { float z = -HALF + i * segLen; ResolveAgainstBoxXZ(g, MakeModelWorldAABB(wall, (Vector3){ border, 0.0f, z }, scl, 90.0f)); ResolveAgainstBoxXZ(g, MakeModelWorldAABB(wall, (Vector3){ -border, 0.0f, z }, scl, 90.0f)); } }