tunnel_runner.c (21353B)
1 #include <assert.h> 2 #include <inttypes.h> 3 #include <SDL2/SDL.h> 4 #include <stdbool.h> 5 #include <stdio.h> 6 #include <stdint.h> 7 #include <time.h> 8 9 #define TR_PI32 3.14159265359f 10 11 #define TR_SCREEN_WIDTH 640 12 #define TR_SCREEN_HEIGHT 480 13 #define TR_TEX_WIDTH 256 14 #define TR_TEX_HEIGHT 256 15 #define TR_BYTES_PER_PIXEL 4 16 #define TR_MAX_CONTROLLERS 4 17 #define TR_MOVEMENT_SPEED 5 18 #define TR_CONTROLLER_STICK_MAX 32770 19 #define TR_CONTROLLER_STICK_MIN -32770 20 21 // TODO: Should time be stored in a double of seconds? 22 #define TR_SECOND 1000 23 #define TR_FPS 60 24 #define TR_MS_PER_FRAME (TR_SECOND / TR_FPS) 25 #define TR_UPDATES_PER_SECOND 120 26 #define TR_MS_PER_UPDATE (TR_SECOND / TR_UPDATES_PER_SECOND) 27 28 #define TR_LOG_ERR(message, ...) fprintf(stderr, (message), ##__VA_ARGS__) 29 30 #ifdef TR_LOGLEVEL_DEBUG 31 #define TR_LOG_DBG(message, ...) printf((message), ##__VA_ARGS__) 32 #else 33 #define TR_LOG_DBG(message, ...) 34 #endif 35 36 #ifdef TR_LOGLEVEL_DEBUG_FRAME 37 #define TR_LOG_FRM(message, ...) TR_LOG_DBG((message), ##__VA_ARGS__) 38 #else 39 #define TR_LOG_FRM(message, ...) 40 #endif 41 42 enum Color 43 { 44 COLOR_GREEN, 45 COLOR_RED, 46 COLOR_BLUE, 47 COLOR_YELLOW, 48 COLOR_MAGENTA, 49 COLOR_CYAN, 50 COLOR_WHITE 51 }; 52 53 struct SDLOffscreenBuffer 54 { 55 // pixels are always 32-bits wide. Memory order: BB GG RR XX. 56 SDL_Texture *texture; 57 void *memory; 58 uint32_t width; 59 uint32_t height; 60 int32_t pitch; 61 }; 62 63 struct SDLWindowDimension 64 { 65 int32_t width; 66 int32_t height; 67 }; 68 69 struct TransformData 70 { 71 int32_t width; 72 int32_t height; 73 int32_t **distance_table; 74 int32_t **angle_table; 75 int32_t look_shift_x; 76 int32_t look_shift_y; 77 }; 78 79 static struct SDLOffscreenBuffer global_back_buffer; 80 static SDL_GameController *controller_handles[TR_MAX_CONTROLLERS]; 81 static SDL_Haptic *rumble_handles[TR_MAX_CONTROLLERS]; 82 static struct TransformData transform; 83 84 85 uint64_t 86 get_current_time_ms(void) 87 { 88 struct timespec current; 89 clock_gettime(CLOCK_MONOTONIC, ¤t); 90 uint64_t milliseconds = ((current.tv_sec * 1000000000) + current.tv_nsec) / 1000000; 91 return milliseconds; 92 } 93 94 95 void 96 render_texture( 97 struct SDLOffscreenBuffer buffer, 98 uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH], 99 int32_t x_offset, 100 int32_t y_offset, 101 enum Color color_choice) 102 { 103 uint8_t *row = (uint8_t *)buffer.memory; 104 105 for (uint32_t y = 0; y < buffer.height; ++y) 106 { 107 uint32_t *pixel = (uint32_t *)row; 108 for (uint32_t x = 0; x < buffer.width; ++x) 109 { 110 uint8_t color = texture[ 111 (uint32_t)(y + y_offset) % TR_TEX_HEIGHT 112 ] 113 [ 114 (uint32_t)(x + x_offset) % TR_TEX_WIDTH 115 ]; 116 uint32_t red = color << 16; 117 uint32_t green = color << 8; 118 uint32_t blue = color; 119 120 switch(color_choice) 121 { 122 case COLOR_GREEN: 123 { 124 *pixel++ = green; 125 } break; 126 case COLOR_RED: 127 { 128 *pixel++ = red; 129 } break; 130 case COLOR_BLUE: 131 { 132 *pixel++ = blue; 133 } break; 134 case COLOR_YELLOW: 135 { 136 *pixel++ = red | green; 137 } break; 138 case COLOR_MAGENTA: 139 { 140 *pixel++ = red | blue; 141 } break; 142 case COLOR_CYAN: 143 { 144 *pixel++ = blue | green; 145 } break; 146 case COLOR_WHITE: 147 { 148 *pixel++ = red | green | blue; 149 } break; 150 default: 151 { 152 TR_LOG_ERR("Invalid color enum value: %d\n", color_choice); 153 *pixel++ = red | green | blue; 154 } break; 155 } 156 } 157 158 row += buffer.pitch; 159 } 160 } 161 162 163 void 164 render_tunnel( 165 struct SDLOffscreenBuffer buffer, 166 uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH], 167 int32_t rotation_offset, 168 int32_t translation_offset, 169 enum Color color_choice) 170 { 171 uint8_t *row = (uint8_t *)buffer.memory; 172 173 for (uint32_t y = 0; y < buffer.height; ++y) 174 { 175 uint32_t *pixel = (uint32_t *)row; 176 for (uint32_t x = 0; x < buffer.width; ++x) 177 { 178 uint32_t texel_y = (uint32_t)(transform.distance_table[y + transform.look_shift_y][x + transform.look_shift_x] + translation_offset) % TR_TEX_HEIGHT; 179 uint32_t texel_x = (uint32_t)(transform.angle_table[y + transform.look_shift_y][x + transform.look_shift_x] + rotation_offset) % TR_TEX_WIDTH; 180 181 assert(texel_x >= 0 && texel_x < TR_TEX_WIDTH); 182 assert(texel_y >= 0 && texel_y < TR_TEX_HEIGHT); 183 184 uint8_t texel = texture[texel_y][texel_x]; 185 186 uint32_t red = texel << 16; 187 uint32_t green = texel << 8; 188 uint32_t blue = texel; 189 190 switch(color_choice) 191 { 192 case COLOR_GREEN: 193 { 194 *pixel++ = green; 195 } break; 196 case COLOR_RED: 197 { 198 *pixel++ = red; 199 } break; 200 case COLOR_BLUE: 201 { 202 *pixel++ = blue; 203 } break; 204 case COLOR_YELLOW: 205 { 206 *pixel++ = red | green; 207 } break; 208 case COLOR_MAGENTA: 209 { 210 *pixel++ = red | blue; 211 } break; 212 case COLOR_CYAN: 213 { 214 *pixel++ = blue | green; 215 } break; 216 case COLOR_WHITE: 217 { 218 *pixel++ = red | green | blue; 219 } break; 220 default: 221 { 222 *pixel++ = red | green | blue; 223 TR_LOG_ERR("Invalid color enum value: %d\n", color_choice); 224 } break; 225 } 226 } 227 row += global_back_buffer.pitch; 228 } 229 } 230 231 232 struct SDLWindowDimension 233 sdl_get_window_dimension(SDL_Window *window) 234 { 235 int w = 0; 236 int h = 0; 237 SDL_GetWindowSize(window, &w, &h); 238 struct SDLWindowDimension result = { .width = w, .height = h }; 239 return result; 240 } 241 242 243 void 244 sdl_resize_texture(struct SDLOffscreenBuffer *buffer, SDL_Renderer *renderer, int32_t window_width, int32_t window_height) 245 { 246 if (buffer->memory) 247 { 248 free(buffer->memory); 249 } 250 251 if (buffer->texture) 252 { 253 SDL_DestroyTexture(buffer->texture); 254 } 255 256 if (transform.distance_table) 257 { 258 for (int32_t y = 0; y < transform.height; ++y) 259 { 260 free(transform.distance_table[y]); 261 } 262 free(transform.distance_table); 263 } 264 if (transform.angle_table) 265 { 266 for (int32_t y = 0; y < transform.height; ++y) 267 { 268 free(transform.angle_table[y]); 269 } 270 free(transform.angle_table); 271 } 272 273 buffer->texture = SDL_CreateTexture( 274 renderer, 275 SDL_PIXELFORMAT_ARGB8888, 276 SDL_TEXTUREACCESS_STREAMING, 277 window_width, window_height); 278 279 buffer->width = window_width; 280 buffer->height = window_height; 281 buffer->pitch = window_width * TR_BYTES_PER_PIXEL; 282 283 buffer->memory = malloc(window_width * window_height * TR_BYTES_PER_PIXEL); 284 285 transform.width = 2 * window_width; 286 transform.height = 2 * window_height; 287 transform.look_shift_x = window_width / 2; 288 transform.look_shift_y = window_height / 2; 289 transform.distance_table = malloc(transform.height * sizeof(int32_t *)); 290 transform.angle_table = malloc(transform.height * sizeof(int32_t *)); 291 292 for (int32_t y = 0; y < transform.height; ++y) 293 { 294 transform.distance_table[y] = malloc(transform.width * sizeof(int32_t)); 295 transform.angle_table[y] = malloc(transform.width * sizeof(int32_t)); 296 } 297 298 // Make distance and angle transformation tables 299 for (int32_t y = 0; y < transform.height; ++y) 300 { 301 for (int32_t x = 0; x < transform.width; ++x) 302 { 303 int32_t dist_from_center_x = x - window_width; 304 int32_t dist_from_center_y = y - window_height; 305 float dist_from_center = sqrtf((float)(dist_from_center_x * dist_from_center_x + dist_from_center_y * dist_from_center_y)); 306 float angle_from_positive_x_axis = atan2f((float)dist_from_center_y, (float)dist_from_center_x) / TR_PI32; 307 308 float ratio = 32.0f; 309 transform.distance_table[y][x] = (int32_t)(ratio * TR_TEX_HEIGHT / dist_from_center) % TR_TEX_HEIGHT; 310 transform.angle_table[y][x] = (int32_t)(0.5f * TR_TEX_WIDTH * angle_from_positive_x_axis); 311 } 312 } 313 } 314 315 316 void 317 sdl_update_window(SDL_Renderer *renderer, struct SDLOffscreenBuffer buffer) 318 { 319 if (SDL_UpdateTexture(buffer.texture, 0, buffer.memory, buffer.pitch)) 320 { 321 TR_LOG_ERR("SDL_UpdateTexture failed: %s\n", SDL_GetError()); 322 } 323 324 SDL_RenderCopy(renderer, buffer.texture, 0, 0); 325 SDL_RenderPresent(renderer); 326 } 327 328 329 bool 330 handle_event(SDL_Event *event) 331 { 332 bool should_quit = false; 333 334 switch(event->type) 335 { 336 case SDL_QUIT: 337 { 338 TR_LOG_DBG("SDL_QUIT\n"); 339 should_quit = true; 340 } break; 341 342 case SDL_WINDOWEVENT: 343 { 344 switch(event->window.event) 345 { 346 case SDL_WINDOWEVENT_SIZE_CHANGED: 347 { 348 SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); 349 SDL_Renderer *renderer = SDL_GetRenderer(window); 350 TR_LOG_DBG("SDL_WINDOWEVENT_SIZE_CHANGED (%d, %d)\n", event->window.data1, event->window.data2); 351 sdl_resize_texture(&global_back_buffer, renderer, event->window.data1, event->window.data2); 352 } break; 353 354 case SDL_WINDOWEVENT_FOCUS_GAINED: 355 { 356 TR_LOG_DBG("SDL_WINDOWEVENT_FOCUS_GAINED\n"); 357 } break; 358 359 case SDL_WINDOWEVENT_EXPOSED: 360 { 361 SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); 362 SDL_Renderer *renderer = SDL_GetRenderer(window); 363 sdl_update_window(renderer, global_back_buffer); 364 } break; 365 } 366 } break; 367 } 368 return should_quit ; 369 } 370 371 372 void 373 sdl_open_game_controllers() 374 { 375 int32_t num_joysticks = SDL_NumJoysticks(); 376 for (int32_t controller_index = 0; controller_index < num_joysticks; ++controller_index) 377 { 378 if (!SDL_IsGameController(controller_index)) 379 { 380 continue; 381 } 382 383 if (controller_index >= TR_MAX_CONTROLLERS) 384 { 385 break; 386 } 387 388 controller_handles[controller_index] = SDL_GameControllerOpen(controller_index); 389 rumble_handles[controller_index] = SDL_HapticOpen(controller_index); 390 391 if (rumble_handles[controller_index] && SDL_HapticRumbleInit(rumble_handles[controller_index]) != 0) 392 { 393 SDL_HapticClose(rumble_handles[controller_index]); 394 rumble_handles[controller_index] = 0; 395 } 396 } 397 } 398 399 400 void 401 sdl_close_game_controllers() 402 { 403 for (int32_t controller_index = 0; controller_index < TR_MAX_CONTROLLERS; ++controller_index) 404 { 405 if (controller_handles[controller_index]) 406 { 407 SDL_GameControllerClose(controller_handles[controller_index]); 408 } 409 } 410 } 411 412 413 void 414 sdl_cleanup(void) 415 { 416 TR_LOG_DBG("Cleaning up...\n"); 417 sdl_close_game_controllers(); 418 SDL_Quit(); 419 } 420 421 422 int 423 main(void) 424 { 425 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) != 0) 426 { 427 TR_LOG_ERR("SDL_Init failed: %s\n", SDL_GetError()); 428 exit(EXIT_FAILURE); 429 } 430 431 sdl_open_game_controllers(); 432 433 // We register this function only _after_ we init SDL and open the game 434 // controllers! 435 atexit(sdl_cleanup); 436 437 SDL_Window *window = SDL_CreateWindow( 438 "Tunnel Runner", 439 SDL_WINDOWPOS_UNDEFINED, 440 SDL_WINDOWPOS_UNDEFINED, 441 TR_SCREEN_WIDTH, 442 TR_SCREEN_HEIGHT, 443 0); 444 445 if (window) 446 { 447 SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); 448 449 if (renderer) 450 { 451 struct SDLWindowDimension dimension = sdl_get_window_dimension(window); 452 sdl_resize_texture(&global_back_buffer, renderer, dimension.width, dimension.height); 453 454 uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH]; 455 456 for (int32_t y = 0; y < TR_TEX_HEIGHT; ++y) 457 { 458 for (int32_t x = 0; x < TR_TEX_WIDTH; ++x) 459 { 460 // XOR texture: 461 texture[y][x] = (x * 256 / TR_TEX_WIDTH) ^ (y * 256 / TR_TEX_HEIGHT); 462 // Mosaic texture: 463 //texture[y][x] = (x * x * y * y); 464 } 465 } 466 467 bool running = true; 468 int32_t rotation_offset = 0; 469 int32_t translation_offset = 0; 470 enum Color color_choice = COLOR_WHITE; 471 472 uint64_t lag = 0; 473 uint64_t previous_ms = get_current_time_ms(); 474 475 while (running) 476 { 477 uint64_t current_ms = get_current_time_ms(); 478 uint64_t elapsed_ms = current_ms - previous_ms; 479 previous_ms = current_ms; 480 lag += elapsed_ms; 481 TR_LOG_FRM("Lag: %d\n", lag); 482 483 TR_LOG_FRM("%" PRIu64 ", %f\n", lag, TR_MS_PER_UPDATE); 484 // TODO: I don't think we need determinism 485 while (lag >= TR_MS_PER_UPDATE) 486 { 487 SDL_Event event; 488 489 while (SDL_PollEvent(&event)) 490 { 491 running = !handle_event(&event); 492 } 493 494 SDL_PumpEvents(); 495 496 dimension = sdl_get_window_dimension(window); 497 498 const uint8_t *keystate = SDL_GetKeyboardState(0); 499 500 if (keystate[SDL_SCANCODE_A]) 501 { 502 rotation_offset -= TR_MOVEMENT_SPEED; 503 } 504 if (keystate[SDL_SCANCODE_D]) 505 { 506 rotation_offset += TR_MOVEMENT_SPEED; 507 } 508 if (keystate[SDL_SCANCODE_W]) 509 { 510 translation_offset += TR_MOVEMENT_SPEED; 511 } 512 if (keystate[SDL_SCANCODE_S]) 513 { 514 translation_offset -= TR_MOVEMENT_SPEED; 515 } 516 if (keystate[SDL_SCANCODE_LEFT]) 517 { 518 rotation_offset --; 519 } 520 if (keystate[SDL_SCANCODE_RIGHT]) 521 { 522 rotation_offset ++; 523 } 524 if (keystate[SDL_SCANCODE_UP]) 525 { 526 translation_offset ++; 527 } 528 if (keystate[SDL_SCANCODE_DOWN]) 529 { 530 translation_offset --; 531 } 532 533 534 for (int32_t controller_index = 0; controller_index < TR_MAX_CONTROLLERS; ++controller_index) 535 { 536 if (SDL_GameControllerGetAttached(controller_handles[controller_index])) 537 { 538 bool start = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_START); 539 bool back = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_BACK); 540 bool a_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_A); 541 bool b_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_B); 542 bool x_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_X); 543 bool y_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_Y); 544 bool left_shoulder = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_LEFTSHOULDER); 545 bool right_shoulder = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); 546 547 int16_t stick_leftx = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_LEFTX); 548 int16_t stick_lefty = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_LEFTY); 549 int16_t stick_rightx = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_RIGHTX); 550 int16_t stick_righty = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_RIGHTY); 551 552 if (start) 553 { 554 SDL_HapticRumblePlay(rumble_handles[controller_index], 0.5f, 2000); 555 color_choice = '\0'; 556 } 557 else 558 { 559 SDL_HapticRumbleStop(rumble_handles[controller_index]); 560 } 561 562 if (back) 563 { 564 running = false; 565 } 566 567 // NOTE: Buttons select colors. 568 if (a_button) 569 { 570 color_choice = COLOR_GREEN; 571 } 572 if (b_button) 573 { 574 color_choice = COLOR_RED; 575 } 576 if (x_button) 577 { 578 color_choice = COLOR_BLUE; 579 } 580 if (y_button) 581 { 582 color_choice = COLOR_YELLOW; 583 } 584 if (left_shoulder) 585 { 586 color_choice = COLOR_MAGENTA; 587 } 588 if (right_shoulder) 589 { 590 color_choice = COLOR_CYAN; 591 } 592 593 rotation_offset += stick_leftx / 5000; 594 translation_offset -= stick_lefty / 5000; 595 596 int32_t dampened_x_max = dimension.width / 2; 597 int32_t dampened_x_min = -(dimension.width / 2); 598 int32_t dampened_y_max = dimension.height / 2; 599 int32_t dampened_y_min = -(dimension.height / 2); 600 601 int32_t dampened_x = (stick_rightx - TR_CONTROLLER_STICK_MIN) * (dampened_x_max - dampened_x_min) / (TR_CONTROLLER_STICK_MAX - TR_CONTROLLER_STICK_MIN) + dampened_x_min; 602 int32_t dampened_y = (stick_righty - TR_CONTROLLER_STICK_MIN) * (dampened_y_max - dampened_y_min) / (TR_CONTROLLER_STICK_MAX - TR_CONTROLLER_STICK_MIN) + dampened_y_min; 603 604 transform.look_shift_x = dimension.width / 2 + dampened_x; 605 transform.look_shift_y = dimension.height / 2 + dampened_y; 606 TR_LOG_FRM("dimension.width / 2: %d\t damp_x: %d\t raw_x: %d\n", dimension.width / 2, dampened_x, stick_rightx); 607 TR_LOG_FRM("dimension.height / 2: %d\t damp_y: %d\t raw_y: %d\n", dimension.height / 2, dampened_y, stick_righty); 608 } 609 } 610 TR_LOG_FRM("%d, %d\n", translation_offset, rotation_offset); 611 612 TR_LOG_FRM("\t%" PRIu64 ", %f\n", lag, TR_MS_PER_UPDATE); 613 //render_tunnel(global_back_buffer, texture, rotation_offset, translation_offset, color_choice); 614 lag -= TR_MS_PER_UPDATE; 615 } 616 render_tunnel(global_back_buffer, texture, rotation_offset, translation_offset, color_choice); 617 //render_texture(global_back_buffer, texture, rotation_offset, translation_offset, color_choice); 618 sdl_update_window(renderer, global_back_buffer); 619 if (elapsed_ms <= TR_MS_PER_FRAME) 620 { 621 SDL_Delay((TR_MS_PER_FRAME - elapsed_ms)); 622 } 623 } 624 } 625 else 626 { 627 TR_LOG_ERR("SDL_CreateRenderer failed: %s\n", SDL_GetError()); 628 exit(EXIT_FAILURE); 629 } 630 } 631 else 632 { 633 TR_LOG_ERR("SDL_CreateWindow failed: %s\n", SDL_GetError()); 634 exit(EXIT_FAILURE); 635 } 636 637 return 0; 638 }