#include #include #include #include #define PI 3.1415926535f #define PI2 PI / 2 #define PI3 3 * PI / 2 #define DR 0.0174533f // One degree in radians #define gameWidth 320 #define gameHeight 256 #define GAP 20 #define MAP_WIDTH (mapX * mapSize) #define MAP_HEIGHT (mapY * mapSize) #define TOTAL_WIDTH (MAP_WIDTH + GAP + gameWidth) #define TOTAL_HEIGHT (MAP_HEIGHT > gameHeight ? MAP_HEIGHT : gameHeight) // 0° => 0 radians (RIGHT) // 90° => π/2 radians (TOP) // 180° => π radians (LEFT) // 270° => 3π/2 radians (BOTTOM) // 360° => 2π radians (RIGHT) // * Define the world int mapX = 8, mapY = 8, mapSize = 32; // clang-format off int map[] = { 1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,1, 1,0,0,0,0,0,0,1, 1,0,0,0,0,2,0,1, 1,0,0,0,0,0,0,1, 1,1,1,1,1,1,1,1, }; // clang-format on // clang-format off int All_Textures[] = // All 32x32 textures { // Checkboard 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,1,1,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,1,1,1,1,1,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,1,1,1,1,1,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,1,1,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, // Brick 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //Window 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, //Door 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,1,1,1,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,1,1,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,0,0,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,0,0,1,0,0,0, 0,0,0,1,1,1,1, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 1,1,1,1,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,1,0,1, 1,0,1,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,1,1,1,0,1, 1,0,1,1,1,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,0, 0,0,0,0,0,0,0, 0,0,0,0,0,0,1, 1,0,0,0,0,0,0, 0,0,0,0,0,0,0, 0,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,0, }; // clang-format on typedef struct { int w, a, d, s; // Button state on/off } ButtonKeys; ButtonKeys Keys; // * ---------------- HELPERS -------------------------------------------------- // Function to check if a number is a power of 2 int isPowerOfTwo(int n) { return (n > 0) && ((n & (n - 1)) == 0); } float degToRad(float a) { return a * M_PI / 180.0; } float FixAng(float a) { if (a > 359) { a -= 360; } if (a < 0) { a += 360; } return a; } // * Returns the distance between the player and the ray's endpoint // That's the hypotenuse, so we can use the Pythagorean theorem. float distance(float ax, float ay, float bx, float by, float ang) { // SOH - CAH - TOA return (sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay))); } // * ---------------- PLAYER --------------------------------------------------- // Player position (playerX, playerY), delta (playerDeltaX, playerDeltaY), and angle (pa) float playerX, playerY, playerDeltaX, playerDeltaY, playerAngle; // Draw the player on the map void drawPlayer2D() { // Draws the player square glColor3f(1, 1, 0); glPointSize(8); glBegin(GL_POINTS); glVertex2i(playerX, playerY); glEnd(); // Draws the player direction glLineWidth(4); glBegin(GL_LINES); glVertex2i(playerX, playerY); glVertex2i(playerX + playerDeltaX * 5, playerY + playerDeltaY * 5); glEnd(); } // * ---------------- Draw Rays and Walls -------------------------------------- void drawRays2D() { int r, mx, my, mapPoint, dof; // Ensure mapSize is a power of 2, otherwise default or handle error if (!isPowerOfTwo(mapSize)) { fprintf(stderr, "Error: mapSize must be a power of 2 (e.g., 16, 32, 64, 128).\n"); exit(1); } int shiftSize = (int)(log2f((float)mapSize)); // Ray x, y, angle float rayX, rayY, rayAngle; float xo, yo; // Final distance float disT; // Let's initialize the ray angle n degrees from the player's facing position // ra = ray angle; pa = player angle; DR = degree radian; n = degrees to use rayAngle = playerAngle - DR * 30; // rayAngle = FixAng(playerAngle+30); // Make sure to limit the next angle if (rayAngle < 0) { rayAngle += 2 * PI; } if (rayAngle > 2 * PI) { rayAngle -= 2 * PI; } // * Cast n rays for (r = 0; r < 60; r++) { // * --- Check Horizontal Lines --- dof = 0; // We need to get the shortest ray cast in either the vertical or horizontal // distance. So, we'll set a default that's way higher than we'll ever // calculate, and save the horizontal ray's X and Y position. float distanceHor = 1000000, hx = playerX, hy = playerY; // Negative inverse of tangent float aTan = -1.0f / tanf(rayAngle); // Check if the ray is looking up or down. // If it's less than π (e.g., 180°), we're looking down. // ? Looking up (ray angle > π) if (rayAngle > PI) { // Round the ray's Y position to the nearest 64th value. // We can do this by bit-shifting down by `shiftSize`, then bit-shifting back up by // `shiftSize`. Then, we subtract a small amount for accuracy. rayY = (((int)playerY >> shiftSize) << shiftSize) - 0.0001f; // The ray's X value is the distance between the player and the ray's Y // position, times the inverse of tangent, plus the player's X position. rayX = (playerY - rayY) * aTan + playerX; // Now that we have the ray's first grid intersection position, we need // the next X and Y offset for the second grid intersection point. yo = -mapSize; xo = -yo * aTan; } // ? Looking down if (rayAngle < PI) { // This is almost exactly the same as looking up, but we add one grid // unit, instead of subtracting a tiny amount. rayY = (((int)playerY >> shiftSize) << shiftSize) + mapSize; rayX = (playerY - rayY) * aTan + playerX; // This is almost exactly the same as looking up, but we add instead of // subtract. yo = mapSize; xo = -yo * aTan; } // ? Looking straight left or right if (rayAngle == 0.0f || rayAngle == PI) { // It's impossible to ever hit a horizontal line rayX = playerX; rayY = playerY; } int mv = 0, mh = 0; // ? We don't want to get caught in an infinite loop, so use the // depth-of-field to limit our check to a max of 8. while (dof < 8) { // We know the coordinates where the ray will hit the wall, but we need // to know where that is in the map array. // Let's take the ray hit position (rx,ry) and divide it by 64 (bit-shift // 6 places), and use that to find the position in the map's array. mx = (int)(rayX) >> shiftSize; my = (int)(rayY) >> shiftSize; mapPoint = my * mapX + mx; // If the map position is less than the array's size, then we can check it // inside the map. If the value is 1, there's a wall. If we hit a wall, // we're done! if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) { // We hit a wall! mh = map[mapPoint]; // Save the values and calculate the distance from the player hx = rayX; hy = rayY; distanceHor = distance(playerX, playerY, hx, hy, rayAngle); dof = 8; // Break the loop - no need to calculate more. } else { // If we didn't hit a wall, we check the next horizontal line by adding // the x and y offset. rayX += xo; rayY += yo; dof += 1; } } // * --- Check Vertical Lines --- dof = 0; float distanceVert = 1000000, vx = playerX, vy = playerY; // Negative of tangent float nTan = -tanf(rayAngle); // ? Looking left if (rayAngle > PI2 && rayAngle < PI3) { rayX = (((int)playerX >> shiftSize) << shiftSize) - 0.0001f; rayY = (playerX - rayX) * nTan + playerY; xo = -mapSize; yo = -xo * nTan; } // ? Looking right if (rayAngle < PI2 || rayAngle > PI3) { rayX = (((int)playerX >> shiftSize) << shiftSize) + mapSize; rayY = (playerX - rayX) * nTan + playerY; xo = mapSize; yo = -xo * nTan; } // ? Looking straight up or down if (rayAngle == 0.0f || rayAngle == PI) { rayX = playerX; rayY = playerY; } while (dof < 8) { // We know the coordinates where the ray will hit the wall, but we need // to know where that is in the map array. // Let's take the ray hit position (rx,ry) and divide it by 64 (bit-shift // 6 places), and use that to find the position in the map's array. mx = (int)(rayX) >> shiftSize; my = (int)(rayY) >> shiftSize; mapPoint = my * mapX + mx; // If the map position is less than the array's size, then we can check it // inside the map. If the value is 1, there's a wall. If we hit a wall, // we're done! if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) { // We hit a wall! mv = map[mapPoint]; // Save the values and calculate the distance from the player vx = rayX; vy = rayY; distanceVert = distance(playerX, playerY, vx, vy, rayAngle); dof = 8; // Break the loop - no need to calculate more. } else { // If we didn't hit a wall, we check the next horizontal line by adding // the x and y offset. rayX += xo; rayY += yo; dof += 1; } } // * Compare shortest horizontal and vertical line, then use the shortest // This is what actually determines our ray that hits a wall! // Vertical wall hit if (distanceVert < distanceHor) { rayX = vx; rayY = vy; disT = distanceVert; } // Horizontal wall hit if (distanceHor < distanceVert) { rayX = hx; rayY = hy; disT = distanceHor; } // * Draw the ray glColor3f(0, 1, 1); glLineWidth(3); glBegin(GL_LINES); glVertex2i(playerX, playerY); glVertex2i(rayX, rayY); glEnd(); // * Increase the ray angle to draw the next ray rayAngle += DR; if (rayAngle < 0) { rayAngle += 2 * PI; } if (rayAngle > 2 * PI) { rayAngle -= 2 * PI; } } } // * Draws the top-down map with white alls and black floors void drawMap2D() { // glColor3f(0, 1, 1); // glBegin(GL_QUADS); // glVertex2i(0, 0); // glVertex2i(TOTAL_WIDTH, 0); // glVertex2i(TOTAL_WIDTH, (int)(TOTAL_HEIGHT / 2)); // glVertex2i(0, (int)(TOTAL_HEIGHT / 2)); // glEnd(); // glColor3f(0, 0, 1); // glBegin(GL_QUADS); // glVertex2i(0, (int)(TOTAL_HEIGHT / 2)); // glVertex2i(TOTAL_WIDTH, (int)(TOTAL_HEIGHT / 2)); // glVertex2i(TOTAL_WIDTH, TOTAL_HEIGHT); // glVertex2i(0, TOTAL_HEIGHT); // glEnd(); int gridMapSize = mapSize; int x, y, xo, yo; for (y = 0; y < mapY; y++) { for (x = 0; x < mapX; x++) { // Check if it's a wall if (map[y * mapX + x] > 0) { // Color walls (white) glColor3f(1, 1, 1); } else { // Color floors (black) glColor3f(0, 0, 0); } xo = x * gridMapSize; yo = y * gridMapSize; glBegin(GL_QUADS); // Draw each block; the +-1 creates the outline glVertex2i(xo + 1, yo + 1); glVertex2i(xo + 1, yo + gridMapSize - 1); glVertex2i(xo + gridMapSize - 1, yo + gridMapSize - 1); glVertex2i(xo + gridMapSize - 1, yo + 1); glEnd(); } } } // * The actual raycaster void drawRays3D() { int r, mx, my, mapPoint, dof; int gridMapSize = mapSize; // Ensure mapSize is a power of 2, otherwise default or handle error if (!isPowerOfTwo(mapSize)) { fprintf(stderr, "Error: mapSize must be a power of 2 (e.g., 16, 32, 64, 128).\n"); exit(1); } int shiftSize = (int)(log2f((float)mapSize)); // Ray x, y, angle float rayX, rayY, rayAngle; float xo, yo; // Final distance float disT; // Let's initialize the ray angle n degrees from the player's facing position // ra = ray angle; pa = player angle; DR = degree radian; n = degrees to use rayAngle = playerAngle - DR * 30; // Make sure to limit the next angle if (rayAngle < 0) { rayAngle += 2 * PI; } if (rayAngle > 2 * PI) { rayAngle -= 2 * PI; } glPointSize(2.0f); glBegin(GL_POINTS); // * Cast n rays for (r = 0; r < 60; r++) { // * --- Check Horizontal Lines --- dof = 0; // We need to get the shortest ray cast in either the vertical or horizontal // distance. So, we'll set a default that's way higher than we'll ever // calculate, and save the horizontal ray's X and Y position. float distanceHor = 1000000, hx = playerX, hy = playerY; // Negative inverse of tangent float aTan = -1.0f / tanf(rayAngle); // Check if the ray is looking up or down. // If it's less than π (e.g., 180°), we're looking down. // ? Looking up (ray angle > π) if (rayAngle > PI) { // Round the ray's Y position to the nearest 64th value. // We can do this by bit-shifting down by `shiftSize`, then bit-shifting back up by // `shiftSize`. Then, we subtract a small amount for accuracy. rayY = (((int)playerY >> shiftSize) << shiftSize) - 0.0001f; // The ray's X value is the distance between the player and the ray's Y // position, times the inverse of tangent, plus the player's X position. rayX = (playerY - rayY) * aTan + playerX; // Now that we have the ray's first grid intersection position, we need // the next X and Y offset for the second grid intersection point. yo = -gridMapSize; xo = -yo * aTan; } // ? Looking down if (rayAngle < PI) { // This is almost exactly the same as looking up, but we add one grid // unit, instead of subtracting a tiny amount. rayY = (((int)playerY >> shiftSize) << shiftSize) + gridMapSize; rayX = (playerY - rayY) * aTan + playerX; // This is almost exactly the same as looking up, but we add instead of // subtract. yo = gridMapSize; xo = -yo * aTan; } // ? Looking straight left or right if (rayAngle == 0.0f || rayAngle == PI) { // It's impossible to ever hit a horizontal line rayX = playerX; rayY = playerY; } int mv = 0, mh = 0; // ? We don't want to get caught in an infinite loop, so use the // depth-of-field to limit our check to a max of 8. while (dof < 8) { // We know the coordinates where the ray will hit the wall, but we need // to know where that is in the map array. // Let's take the ray hit position (rx,ry) and divide it by 64 (bit-shift // 6 places), and use that to find the position in the map's array. mx = (int)(rayX) >> shiftSize; my = (int)(rayY) >> shiftSize; mapPoint = my * mapX + mx; // If the map position is less than the array's size, then we can check it // inside the map. If the value is 1, there's a wall. If we hit a wall, // we're done! if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) { // We hit a wall! mh = map[mapPoint]; // Save the values and calculate the distance from the player hx = rayX; hy = rayY; distanceHor = distance(playerX, playerY, hx, hy, rayAngle); dof = 8; // Break the loop - no need to calculate more. } else { // If we didn't hit a wall, we check the next horizontal line by adding // the x and y offset. rayX += xo; rayY += yo; dof += 1; } } // * --- Check Vertical Lines --- dof = 0; float distanceVert = 1000000, vx = playerX, vy = playerY; // Negative of tangent float nTan = -tanf(rayAngle); // ? Looking left if (rayAngle > PI2 && rayAngle < PI3) { rayX = (((int)playerX >> shiftSize) << shiftSize) - 0.0001f; rayY = (playerX - rayX) * nTan + playerY; xo = -gridMapSize; yo = -xo * nTan; } // ? Looking right if (rayAngle < PI2 || rayAngle > PI3) { rayX = (((int)playerX >> shiftSize) << shiftSize) + gridMapSize; rayY = (playerX - rayX) * nTan + playerY; xo = gridMapSize; yo = -xo * nTan; } // ? Looking straight up or down if (rayAngle == 0.0f || rayAngle == PI) { rayX = playerX; rayY = playerY; } while (dof < 8) { // We know the coordinates where the ray will hit the wall, but we need // to know where that is in the map array. // Let's take the ray hit position (rx,ry) and divide it by 64 (bit-shift // 6 places), and use that to find the position in the map's array. mx = (int)(rayX) >> shiftSize; my = (int)(rayY) >> shiftSize; mapPoint = my * mapX + mx; // If the map position is less than the array's size, then we can check it // inside the map. If the value is 1, there's a wall. If we hit a wall, // we're done! if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) { // We hit a wall! mv = map[mapPoint]; // Save the values and calculate the distance from the player vx = rayX; vy = rayY; distanceVert = distance(playerX, playerY, vx, vy, rayAngle); dof = 8; // Break the loop - no need to calculate more. } else { // If we didn't hit a wall, we check the next horizontal line by adding // the x and y offset. rayX += xo; rayY += yo; dof += 1; } } // * Compare shortest horizontal and vertical line, then use the shortest // This is what actually determines our ray that hits a wall! // Vertical wall hit if (distanceVert < distanceHor) { rayX = vx; rayY = vy; disT = distanceVert; // Vertical walls are drawn a little brighter than horizontal walls glColor3f(0.9, 0, 0); if (mv == 2) { glColor3f(0, 0, 0.7); } } // Horizontal wall hit if (distanceHor < distanceVert) { rayX = hx; rayY = hy; disT = distanceHor; // Horizontal walls are drawn a little darker than vertical walls glColor3f(0.7, 0, 0); if (mh == 2) { glColor3f(0, 0, 0.4); } } // * --- Draw 3D Walls --- // Correct the fish-eye effect float correctedAngle = playerAngle - rayAngle; if (correctedAngle < 0) { correctedAngle += 2 * PI; } if (correctedAngle > 2 * PI) { correctedAngle -= 2 * PI; } disT = disT * cos(correctedAngle); // The formula for each line height will be the cube size (64) times our // screen height, divided by the ray distance (disT). // The further away, the larger the distance and the shorter the final line // height will be. float lineHeight = (mapSize * gameHeight) / disT; float textureYstep = mapSize / (float)lineHeight; float textureYoffset = 0; if (lineHeight > gameHeight) { textureYoffset = (lineHeight - gameHeight) / 2.0; lineHeight = gameHeight; } // Let's make sure the line doesn't exceed our vertical space, so we cap it if (lineHeight > gameWidth) { lineHeight = gameWidth; } // We need to offset the line to the middle of the screen // That's the full window height, minus half the line height float lineOffset = (gameHeight / 2.0f) - (lineHeight / 2.0f); // Calculate dynamic column width and horizontal starting offset float rayWidth = (float)gameWidth / 60.0f; int xStart = (int)floorf(r * rayWidth); int xEnd = (int)floorf((r + 1) * rayWidth); float rayX3D = MAP_WIDTH + GAP + (r * rayWidth); float shade = 1; glColor3f(0, 0.8, 0); if (distanceVert < distanceHor) { shade = 0.5; rayX = vx; rayY = vy; distanceHor = distanceVert; glColor3f(0, 0.6, 0); } // * Draw the "wall" line int x, y; int textureX; if (shade == 1) // North-south walls (PI -> (2 * PI)) { textureX = (int)(rayX / 2.0) % mapSize; if (rayAngle < PI) // South walls { textureX = mapSize - 1 - textureX; } } else // East-west walls (PI2 -> PI3) { textureX = (int)(rayY / 2.0) % mapSize; if (rayAngle > PI2 && rayAngle < PI3) // West { textureX = mapSize - 1 - textureX; } } for (x = xStart; x < xEnd; x++) { float textureY = textureYoffset * textureYstep; for (y = 0; y < lineHeight; y++) { float c = All_Textures[((int)textureY * mapSize) + textureX] * shade; glColor3f(c, c, c); glVertex2i(MAP_WIDTH + GAP + x, (int)(y + lineOffset)); textureY += textureYstep; } } // * Increase the ray angle to draw the next ray rayAngle += DR; if (rayAngle < 0) { rayAngle += 2 * PI; } if (rayAngle > 2 * PI) { rayAngle -= 2 * PI; } } glEnd(); } // * Draw ceiling void drawCeiling() { float halfHeight = (float)(gameHeight / 2); glBegin(GL_QUADS); for (float distance = 0; distance < halfHeight; distance += 1.0f) { // Normalize color from 0.0 to 1.0 based on distance, multiplied by relative intensity float colorIntensity = 0.5f + (distance / halfHeight); glColor3f(0.25 * colorIntensity, 0.25 * colorIntensity, 0.25 * colorIntensity); // Draw a horizontal strip at the current distance glVertex2f(MAP_WIDTH + GAP, distance); glVertex2f(MAP_WIDTH + GAP, distance + 1.0f); glVertex2f(TOTAL_WIDTH, distance + 1.0f); glVertex2f(TOTAL_WIDTH, distance); } glEnd(); } // * Draw floor void drawFloor() { float halfHeight = (float)(gameHeight / 2); glBegin(GL_QUADS); for (float distance = 0; distance < halfHeight; distance += 1.0f) { float colorIntensity = 0.5f + (distance / halfHeight); glColor3f(0.25 * colorIntensity, 0.25 * colorIntensity, 0.0f); float y = halfHeight + distance; glVertex2f(MAP_WIDTH + GAP, y); glVertex2f(MAP_WIDTH + GAP, y + 1.0f); glVertex2f(TOTAL_WIDTH, y + 1.0f); glVertex2f(TOTAL_WIDTH, y); } glEnd(); } // * ---------------- MAIN ----------------------------------------------------- float frame1, frame2, fps; // * Main rendering void display() { // Frames per second frame2 = glutGet(GLUT_ELAPSED_TIME); if (frame1 < 0) { frame1 = 10; } fps = (frame2 - frame1); frame1 = glutGet(GLUT_ELAPSED_TIME); float movementMultiplier = 0.05 * fps; // * Handle keyboard inputs // ? Move left if (Keys.a == 1) { // We change the angle by a small value because we're not working with // degrees. In C, we instead use radians, which is 0 -> 2π. // // 0° -> 360° ≈ 0 -> ~6.28 // Change player angle slightly (using radians) playerAngle -= 0.1 * movementMultiplier; // If player angle drops below 0, let's reset back to 2π if (playerAngle < 0) { playerAngle += 2 * PI; } // The values returned from sin() and cos() are very small, so we'll just // artificialy inflate them by multilying by 5. This is our actual movement. playerDeltaX = cos(playerAngle) * 5.0f; playerDeltaY = sin(playerAngle) * 5.0f; } // ? Move right if (Keys.d == 1) { playerAngle += 0.1 * movementMultiplier; if (playerAngle > 2 * PI) { playerAngle -= 2 * PI; } playerDeltaX = cos(playerAngle) * 5.0f; playerDeltaY = sin(playerAngle) * 5.0f; } // We need to check if we're going to bump into a wall and prevent that from happening. float movementRestriction = mapSize / 4; int xOffset = 0; if (playerDeltaX < 0) { xOffset = -movementRestriction; } else { xOffset = movementRestriction; } int yOffset = 0; if (playerDeltaY < 0) { yOffset = -movementRestriction; } else { yOffset = movementRestriction; } // Convert pixel positions to map grid coordinates dynamically using mapSize int iPlayerX = (int)playerX / mapSize; int iPlayerY = (int)playerY / mapSize; int iPlayerX_add_xOffset = (int)(playerX + xOffset) / mapSize; int iPlayerY_add_yOffset = (int)(playerY + yOffset) / mapSize; int iPlayerX_sub_xOffset = (int)(playerX - xOffset) / mapSize; int iPlayerY_sub_yOffset = (int)(playerY - yOffset) / mapSize; // ? Move forward if (Keys.w == 1) { // We change the X and Y values based on our delta, which is calculated // during our rotation. if (map[iPlayerY * mapX + iPlayerX_add_xOffset] == 0) { playerX += playerDeltaX * movementMultiplier; } if (map[iPlayerY_add_yOffset * mapX + iPlayerX] == 0) { playerY += playerDeltaY * movementMultiplier; } } // ? Move backwards if (Keys.s == 1) { if (map[iPlayerY * mapX + iPlayerX_sub_xOffset] == 0) { playerX -= playerDeltaX * movementMultiplier; } if (map[iPlayerY_sub_yOffset * mapX + iPlayerX] == 0) { playerY -= playerDeltaY * movementMultiplier; } } glutPostRedisplay(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawMap2D(); drawRays2D(); drawPlayer2D(); drawCeiling(); drawFloor(); drawRays3D(); glutSwapBuffers(); } // * Key input handling void ButtonDown(unsigned char key, int x, int y) { if (key == 'a') { Keys.a = 1; } if (key == 'd') { Keys.d = 1; } if (key == 'w') { Keys.w = 1; } if (key == 's') { Keys.s = 1; } glutPostRedisplay(); } void ButtonUp(unsigned char key, int x, int y) { if (key == 'a') { Keys.a = 0; } if (key == 'd') { Keys.d = 0; } if (key == 'w') { Keys.w = 0; } if (key == 's') { Keys.s = 0; } glutPostRedisplay(); } // * Window resizing void resize(int w, int h) { // Prevent division by zero if (h == 0) h = 1; // Map the orthographic scene bounds to fill the entire window frame glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Set logical coordinate space: (left, right, bottom, top) gluOrtho2D(0, TOTAL_WIDTH, TOTAL_HEIGHT, 0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } // * Check for key presses and move the "player" accordingly void init() { // Sets the screen to the given color (grey) glClearColor(0.3, 0.3, 0.3, 0); // Sets the window to be 2D at a given size // gluOrtho2D(0, 1024, 512, 0); // gluOrtho2D(0, TOTAL_WIDTH, TOTAL_HEIGHT, 0); // Spawn player centered in grid tile (1, 1) playerX = 1.5f * mapSize; playerY = 1.5f * mapSize; playerAngle = 0; // Calculate the initial player deltaX and deltaY playerDeltaX = cos(playerAngle) * 5.0f; playerDeltaY = sin(playerAngle) * 5.0f; } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); // glutInitWindowSize(TOTAL_WIDTH, TOTAL_HEIGHT); glutInitWindowSize(1024, 512); // Initialize the window to the center of the screen glutInitWindowPosition(200, 200); glutCreateWindow("Raycaster"); init(); // Send display parameters to the display function glutDisplayFunc(display); // Handle window resizes glutReshapeFunc(resize); // Send key presses to the keyboard function glutKeyboardFunc(ButtonDown); glutKeyboardUpFunc(ButtonUp); glutMainLoop(); }