├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── img ├── logo.PNG └── peekgif.gif └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | littlewolf 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gustav Louw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = littlewolf 2 | 3 | CFLAGS = -std=c99 -Wall -Wextra -pedantic -Ofast -flto -march=native 4 | 5 | LDFLAGS = -lm -lSDL2 6 | 7 | CC = gcc 8 | 9 | SRC = main.c 10 | 11 | all: 12 | $(CC) $(CFLAGS) $(SRC) $(LDFLAGS) -o $(BIN) 13 | 14 | run: 15 | ./$(BIN) 16 | 17 | clean: 18 | rm -f $(BIN) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![screenshot](img/logo.PNG) 2 | 3 | Littlewolf aims to be a very minimalistic software graphics 4 | engine reminiscent to some of the early works of Carmack at id. 5 | 6 | make; ./littlewolf 7 | 8 | Dependencies: 9 | 10 | SDL2-devel 11 | 12 | Controls: 13 | 14 | move: W,A,S,D 15 | 16 | turn: H,L 17 | 18 | exit: END, ESCAPE 19 | 20 | ![screenshot](img/peekgif.gif) 21 | -------------------------------------------------------------------------------- /img/logo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/littlewolf/6e66e1e7f6adb39e321e4a51d1866c252b53673d/img/logo.PNG -------------------------------------------------------------------------------- /img/peekgif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/littlewolf/6e66e1e7f6adb39e321e4a51d1866c252b53673d/img/peekgif.gif -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct 7 | { 8 | float x; 9 | float y; 10 | } 11 | Point; 12 | 13 | typedef struct 14 | { 15 | int tile; 16 | Point where; 17 | } 18 | Hit; 19 | 20 | typedef struct 21 | { 22 | Point a; 23 | Point b; 24 | } 25 | Line; 26 | 27 | typedef struct 28 | { 29 | SDL_Window* window; 30 | SDL_Renderer* renderer; 31 | SDL_Texture* texture; 32 | int xres; 33 | int yres; 34 | } 35 | Gpu; 36 | 37 | typedef struct 38 | { 39 | uint32_t* pixels; 40 | int width; 41 | } 42 | Display; 43 | 44 | typedef struct 45 | { 46 | int top; 47 | int bot; 48 | float size; 49 | } 50 | Wall; 51 | 52 | typedef struct 53 | { 54 | Line fov; 55 | Point where; 56 | Point velocity; 57 | float speed; 58 | float acceleration; 59 | float theta; 60 | } 61 | Hero; 62 | 63 | typedef struct 64 | { 65 | const char** ceiling; 66 | const char** walling; 67 | const char** floring; 68 | } 69 | Map; 70 | 71 | // Rotates the player by some radian value. 72 | static Point turn(const Point a, const float t) 73 | { 74 | const Point b = { a.x * cosf(t) - a.y * sinf(t), a.x * sinf(t) + a.y * cosf(t) }; 75 | return b; 76 | } 77 | 78 | // Rotates a point 90 degrees. 79 | static Point rag(const Point a) 80 | { 81 | const Point b = { -a.y, a.x }; 82 | return b; 83 | } 84 | 85 | // Subtracts two points. 86 | static Point sub(const Point a, const Point b) 87 | { 88 | const Point c = { a.x - b.x, a.y - b.y }; 89 | return c; 90 | } 91 | 92 | // Adds two points. 93 | static Point add(const Point a, const Point b) 94 | { 95 | const Point c = { a.x + b.x, a.y + b.y }; 96 | return c; 97 | } 98 | 99 | // Multiplies a point by a scalar value. 100 | static Point mul(const Point a, const float n) 101 | { 102 | const Point b = { a.x * n, a.y * n }; 103 | return b; 104 | } 105 | 106 | // Returns the magnitude of a point. 107 | static float mag(const Point a) 108 | { 109 | return sqrtf(a.x * a.x + a.y * a.y); 110 | } 111 | 112 | // Returns the unit vector of a point. 113 | static Point unit(const Point a) 114 | { 115 | return mul(a, 1.0f / mag(a)); 116 | } 117 | 118 | // Returns the slope of a point. 119 | static float slope(const Point a) 120 | { 121 | return a.y / a.x; 122 | } 123 | 124 | // Fast floor (math.h is too slow). 125 | static int fl(const float x) 126 | { 127 | return (int) x - (x < (int) x); 128 | } 129 | 130 | // Fast ceil (math.h is too slow). 131 | static int cl(const float x) 132 | { 133 | return (int) x + (x > (int) x); 134 | } 135 | 136 | // Steps horizontally along a square grid. 137 | static Point sh(const Point a, const Point b) 138 | { 139 | const float x = b.x > 0.0f ? fl(a.x + 1.0f) : cl(a.x - 1.0f); 140 | const float y = slope(b) * (x - a.x) + a.y; 141 | const Point c = { x, y }; 142 | return c; 143 | } 144 | 145 | // Steps vertically along a square grid. 146 | static Point sv(const Point a, const Point b) 147 | { 148 | const float y = b.y > 0.0f ? fl(a.y + 1.0f) : cl(a.y - 1.0f); 149 | const float x = (y - a.y) / slope(b) + a.x; 150 | const Point c = { x, y }; 151 | return c; 152 | } 153 | 154 | // Returns a decimal value of the ascii tile value on the map. 155 | static int tile(const Point a, const char** const tiles) 156 | { 157 | const int x = a.x; 158 | const int y = a.y; 159 | return tiles[y][x] - '0'; 160 | } 161 | 162 | // Floating point decimal. 163 | static float dec(const float x) 164 | { 165 | return x - (int) x; 166 | } 167 | 168 | // Casts a ray from in unit until a tile is hit. 169 | static Hit cast(const Point where, const Point direction, const char** const walling) 170 | { 171 | // Determine whether to step horizontally or vertically on the grid. 172 | const Point hor = sh(where, direction); 173 | const Point ver = sv(where, direction); 174 | const Point ray = mag(sub(hor, where)) < mag(sub(ver, where)) ? hor : ver; 175 | // Due to floating point error, the step may not make it to the next grid square. 176 | // Three directions (dy, dx, dc) of a tiny step will be added to the ray 177 | // depending on if the ray hit a horizontal wall, a vertical wall, or the corner 178 | // of two walls, respectively. 179 | const Point dc = mul(direction, 0.01f); 180 | const Point dx = { dc.x, 0.0f }; 181 | const Point dy = { 0.0f, dc.y }; 182 | const Point test = add(ray, 183 | // Tiny step for corner of two grid squares. 184 | mag(sub(hor, ver)) < 1e-3f ? dc : 185 | // Tiny step for vertical grid square. 186 | dec(ray.x) == 0.0f ? dx : 187 | // Tiny step for a horizontal grid square. 188 | dy); 189 | const Hit hit = { tile(test, walling), ray }; 190 | // If a wall was not hit, then continue advancing the ray. 191 | return hit.tile ? hit : cast(ray, direction, walling); 192 | } 193 | 194 | // Party casting. Returns a percentage of related to for ceiling and 195 | // floor casting when lerping the floor or ceiling. 196 | static float pcast(const float size, const int yres, const int y) 197 | { 198 | return size / (2 * (y + 1) - yres); 199 | } 200 | 201 | // Rotates a line by some radian amount. 202 | static Line rotate(const Line l, const float t) 203 | { 204 | const Line line = { turn(l.a, t), turn(l.b, t) }; 205 | return line; 206 | } 207 | 208 | // Linear interpolation. 209 | static Point lerp(const Line l, const float n) 210 | { 211 | return add(l.a, mul(sub(l.b, l.a), n)); 212 | } 213 | 214 | // Setups the software gpu. 215 | static Gpu setup(const int xres, const int yres, const bool vsync) 216 | { 217 | if (SDL_Init(SDL_INIT_VIDEO) != 0) 218 | { 219 | puts(SDL_GetError()); 220 | exit(1); 221 | } 222 | SDL_Window* const window = SDL_CreateWindow( 223 | "littlewolf", 224 | SDL_WINDOWPOS_UNDEFINED, 225 | SDL_WINDOWPOS_UNDEFINED, 226 | xres, yres, 227 | SDL_WINDOW_SHOWN); 228 | SDL_Renderer* const renderer = SDL_CreateRenderer( 229 | window, 230 | -1, 231 | SDL_RENDERER_ACCELERATED | (vsync ? SDL_RENDERER_PRESENTVSYNC : 0x0)); 232 | // Notice the flip between xres and yres. 233 | // The texture is 90 degrees flipped on its side for fast cache access. 234 | SDL_Texture* const texture = SDL_CreateTexture( 235 | renderer, 236 | SDL_PIXELFORMAT_ARGB8888, 237 | SDL_TEXTUREACCESS_STREAMING, 238 | yres, xres); 239 | if(window == NULL || renderer == NULL || texture == NULL) 240 | { 241 | puts(SDL_GetError()); 242 | exit(1); 243 | } 244 | const Gpu gpu = { window, renderer, texture, xres, yres }; 245 | return gpu; 246 | } 247 | 248 | // Presents the software gpu to the window. 249 | // Calls the real GPU to rotate texture back 90 degrees before presenting. 250 | static void present(const Gpu gpu) 251 | { 252 | const SDL_Rect dst = { 253 | (gpu.xres - gpu.yres) / 2, 254 | (gpu.yres - gpu.xres) / 2, 255 | gpu.yres, gpu.xres, 256 | }; 257 | SDL_RenderCopyEx(gpu.renderer, gpu.texture, NULL, &dst, -90, NULL, SDL_FLIP_NONE); 258 | SDL_RenderPresent(gpu.renderer); 259 | } 260 | 261 | // Locks the gpu, returning a pointer to video memory. 262 | static Display lock(const Gpu gpu) 263 | { 264 | void* screen; 265 | int pitch; 266 | SDL_LockTexture(gpu.texture, NULL, &screen, &pitch); 267 | const Display display = { (uint32_t*) screen, pitch / (int) sizeof(uint32_t) }; 268 | return display; 269 | } 270 | 271 | // Places a pixels in gpu video memory. 272 | static void put(const Display display, const int x, const int y, const uint32_t pixel) 273 | { 274 | display.pixels[y + x * display.width] = pixel; 275 | } 276 | 277 | // Unlocks the gpu, making the pointer to video memory ready for presentation 278 | static void unlock(const Gpu gpu) 279 | { 280 | SDL_UnlockTexture(gpu.texture); 281 | } 282 | 283 | // Spins the hero when keys h,l are held down. 284 | static Hero spin(Hero hero, const uint8_t* key) 285 | { 286 | if(key[SDL_SCANCODE_H]) hero.theta -= 0.1f; 287 | if(key[SDL_SCANCODE_L]) hero.theta += 0.1f; 288 | return hero; 289 | } 290 | 291 | // Moves the hero when w,a,s,d are held down. Handles collision detection for the walls. 292 | static Hero move(Hero hero, const char** const walling, const uint8_t* key) 293 | { 294 | const Point last = hero.where, zero = { 0.0f, 0.0f }; 295 | // Accelerates with key held down. 296 | if(key[SDL_SCANCODE_W] || key[SDL_SCANCODE_S] || key[SDL_SCANCODE_D] || key[SDL_SCANCODE_A]) 297 | { 298 | const Point reference = { 1.0f, 0.0f }; 299 | const Point direction = turn(reference, hero.theta); 300 | const Point acceleration = mul(direction, hero.acceleration); 301 | if(key[SDL_SCANCODE_W]) hero.velocity = add(hero.velocity, acceleration); 302 | if(key[SDL_SCANCODE_S]) hero.velocity = sub(hero.velocity, acceleration); 303 | if(key[SDL_SCANCODE_D]) hero.velocity = add(hero.velocity, rag(acceleration)); 304 | if(key[SDL_SCANCODE_A]) hero.velocity = sub(hero.velocity, rag(acceleration)); 305 | } 306 | // Otherwise, decelerates (exponential decay). 307 | else hero.velocity = mul(hero.velocity, 1.0f - hero.acceleration / hero.speed); 308 | // Caps velocity if top speed is exceeded. 309 | if(mag(hero.velocity) > hero.speed) hero.velocity = mul(unit(hero.velocity), hero.speed); 310 | // Moves. 311 | hero.where = add(hero.where, hero.velocity); 312 | // Sets velocity to zero if there is a collision and puts hero back in bounds. 313 | if(tile(hero.where, walling)) 314 | { 315 | hero.velocity = zero; 316 | hero.where = last; 317 | } 318 | return hero; 319 | } 320 | 321 | // Returns a color value (RGB) from a decimal tile value. 322 | static uint32_t color(const int tile) 323 | { 324 | switch(tile) 325 | { 326 | default: 327 | case 1: return 0x00AA0000; // Red. 328 | case 2: return 0x0000AA00; // Green. 329 | case 3: return 0x000000AA; // Blue. 330 | } 331 | } 332 | 333 | // Calculates wall size using the ray to the wall. 334 | static Wall project(const int xres, const int yres, const float focal, const Point corrected) 335 | { 336 | // Normal distance of corrected ray is clamped to some small value else wall size will shoot to infinity. 337 | const float normal = corrected.x < 1e-2f ? 1e-2f : corrected.x; 338 | const float size = 0.5f * focal * xres / normal; 339 | const int top = (yres + size) / 2.0f; 340 | const int bot = (yres - size) / 2.0f; 341 | // Top and bottom values are clamped to screen size else renderer will waste cycles 342 | // (or segfault) when rasterizing pixels off screen. 343 | const Wall wall = { top > yres ? yres : top, bot < 0 ? 0 : bot, size }; 344 | return wall; 345 | } 346 | 347 | // Renders the entire scene from the perspective given a and a software . 348 | static void render(const Hero hero, const Map map, const Gpu gpu) 349 | { 350 | const int t0 = SDL_GetTicks(); 351 | const Line camera = rotate(hero.fov, hero.theta); 352 | const Display display = lock(gpu); 353 | // Ray cast for all columns of the window. 354 | for(int x = 0; x < gpu.xres; x++) 355 | { 356 | const Point direction = lerp(camera, x / (float) gpu.xres); 357 | const Hit hit = cast(hero.where, direction, map.walling); 358 | const Point ray = sub(hit.where, hero.where); 359 | const Line trace = { hero.where, hit.where }; 360 | const Point corrected = turn(ray, -hero.theta); 361 | const Wall wall = project(gpu.xres, gpu.yres, hero.fov.a.x, corrected); 362 | // Renders flooring. 363 | for(int y = 0; y < wall.bot; y++) 364 | put(display, x, y, color(tile(lerp(trace, -pcast(wall.size, gpu.yres, y)), map.floring))); 365 | // Renders wall. 366 | for(int y = wall.bot; y < wall.top; y++) 367 | put(display, x, y, color(hit.tile)); 368 | // Renders ceiling. 369 | for(int y = wall.top; y < gpu.yres; y++) 370 | put(display, x, y, color(tile(lerp(trace, +pcast(wall.size, gpu.yres, y)), map.ceiling))); 371 | } 372 | unlock(gpu); 373 | present(gpu); 374 | // Caps frame rate to ~60 fps if the vertical sync (VSYNC) init failed. 375 | const int t1 = SDL_GetTicks(); 376 | const int ms = 16 - (t1 - t0); 377 | SDL_Delay(ms < 0 ? 0 : ms); 378 | } 379 | 380 | static bool done() 381 | { 382 | SDL_Event event; 383 | SDL_PollEvent(&event); 384 | return event.type == SDL_QUIT 385 | || event.key.keysym.sym == SDLK_END 386 | || event.key.keysym.sym == SDLK_ESCAPE; 387 | } 388 | 389 | // Changes the field of view. A focal value of 1.0 is 90 degrees. 390 | static Line viewport(const float focal) 391 | { 392 | const Line fov = { 393 | { focal, -1.0f }, 394 | { focal, +1.0f }, 395 | }; 396 | return fov; 397 | } 398 | 399 | static Hero born(const float focal) 400 | { 401 | const Hero hero = { 402 | viewport(focal), 403 | // Where. 404 | { 3.5f, 3.5f }, 405 | // Velocity. 406 | { 0.0f, 0.0f }, 407 | // Speed. 408 | 0.10f, 409 | // Acceleration. 410 | 0.015f, 411 | // Theta radians. 412 | 0.0f 413 | }; 414 | return hero; 415 | } 416 | 417 | // Builds the map. Note the static prefix for the parties. Map lives in .bss in private. 418 | static Map build() 419 | { 420 | static const char* ceiling[] = { 421 | "111111111111111111111111111111111111111111111", 422 | "122223223232232111111111111111222232232322321", 423 | "122222221111232111111111111111222222211112321", 424 | "122221221232323232323232323232222212212323231", 425 | "122222221111232111111111111111222222211112321", 426 | "122223223232232111111111111111222232232322321", 427 | "111111111111111111111111111111111111111111111", 428 | }; 429 | static const char* walling[] = { 430 | "111111111111111111111111111111111111111111111", 431 | "100000000000000111111111111111000000000000001", 432 | "103330001111000111111111111111033300011110001", 433 | "103000000000000000000000000000030000030000001", 434 | "103330001111000111111111111111033300011110001", 435 | "100000000000000111111111111111000000000000001", 436 | "111111111111111111111111111111111111111111111", 437 | }; 438 | static const char* floring[] = { 439 | "111111111111111111111111111111111111111111111", 440 | "122223223232232111111111111111222232232322321", 441 | "122222221111232111111111111111222222211112321", 442 | "122222221232323323232323232323222222212323231", 443 | "122222221111232111111111111111222222211112321", 444 | "122223223232232111111111111111222232232322321", 445 | "111111111111111111111111111111111111111111111", 446 | }; 447 | const Map map = { ceiling, walling, floring }; 448 | return map; 449 | } 450 | 451 | // Get Psyched! 452 | int main(int argc, char* argv[]) 453 | { 454 | (void) argc; 455 | (void) argv; 456 | const Gpu gpu = setup(700, 400, true); 457 | const Map map = build(); 458 | Hero hero = born(0.8f); 459 | while(!done()) 460 | { 461 | const uint8_t* key = SDL_GetKeyboardState(NULL); 462 | hero = spin(hero, key); 463 | hero = move(hero, map.walling, key); 464 | render(hero, map, gpu); 465 | } 466 | // No need to free anything - gives quick exit. 467 | return 0; 468 | } 469 | --------------------------------------------------------------------------------