1095 lines
42 KiB
C
1095 lines
42 KiB
C
#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;
|
||
int nearAcorn;
|
||
} 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
|
||
|
||
float square(float num);
|
||
|
||
//------------------------------------------------------------------------------------
|
||
// 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;
|
||
g.nearAcorn = 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, -9.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);
|
||
|
||
int player_acorn_diff = square(g.player.position.x - acorn_position.x) + square(g.player.position.z - acorn_position.z);
|
||
|
||
if (player_acorn_diff <= 3)
|
||
{
|
||
g.nearAcorn = 1;
|
||
}
|
||
else
|
||
{
|
||
g.nearAcorn = 0;
|
||
}
|
||
|
||
if (g.nearAcorn == 1 && IsKeyPressed(KEY_E))
|
||
{
|
||
acorn_position.x = 15;
|
||
acorn_position.z = 15;
|
||
}
|
||
|
||
// 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)", acorn_position.x, acorn_position.y, acorn_position.z),
|
||
15, 75, 20, BLACK
|
||
);
|
||
|
||
|
||
if (g.nearAcorn == 1)
|
||
{
|
||
int acorn_text_width = MeasureText("Press E to Collect the Acorn", 50);
|
||
DrawText("Press E to Collect the Acorn", (screenWidth - acorn_text_width)/2, screenHeight * 0.8, 50, PURPLE);
|
||
}
|
||
|
||
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));
|
||
}
|
||
}
|
||
|
||
float square(float num)
|
||
{
|
||
float out = num * num;
|
||
return out;
|
||
} |