Files
Test_Game/main.c
2025-09-01 15:49:07 -05:00

764 lines
30 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "raylib.h"
#include "rcamera.h"
#include "raymath.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdlib.h"
#include "stdint.h"
#include "rlgl.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
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);
//------------------------------------------------------------------------------------
// 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 model = LoadModel("resources/3d_models/bad_sphere.glb");
//Vector3 position = { 0.0f, 0.0f, 0.0f };
// Load a 2D image as texture for the billboard
//Billboards always point toward the active camera.
Texture2D sign = LoadTexture("resources/sprites/husk/ZOMBC1.png");
GenTextureMipmaps(&sign);
SetTextureFilter(sign, TEXTURE_FILTER_BILINEAR); // 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-of-war 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
// 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 = 100.0f;
float lightFade = 1.0f;
float intensity = 0.25f; // try 0.71.5 to taste
float beamSpread = 0.05f; // try 0.150.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);
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;
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);
RenderTrees(&g, tree, treepos, treeSize);
DrawBillboard(g.player.camera, sign, bbPos, bbSize, WHITE);
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);
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);
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; // 140
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;
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);
}
}
}