tunnel_runner.c (21256B)
1 #include <assert.h> 2 #include <inttypes.h> 3 #include "SDL.h" 4 #include <stdbool.h> 5 #include <stdio.h> 6 #include <stdint.h> 7 #include <math.h> 8 #include <time.h> 9 10 #define TR_PI32 3.14159265359f 11 12 #define TR_SCREEN_WIDTH 640 13 #define TR_SCREEN_HEIGHT 480 14 #define TR_TEX_WIDTH 256 15 #define TR_TEX_HEIGHT 256 16 #define TR_BYTES_PER_PIXEL 4 17 #define TR_MAX_CONTROLLERS 4 18 #define TR_MOVEMENT_SPEED 5 19 #define TR_CONTROLLER_STICK_MAX 32770 20 #define TR_CONTROLLER_STICK_MIN -32770 21 22 // TODO: Should time be stored in a double of seconds? 23 #define TR_SECOND 1000 24 #define TR_FPS 60 25 #define TR_MS_PER_FRAME (TR_SECOND / TR_FPS) 26 #define TR_UPDATES_PER_SECOND 120 27 #define TR_MS_PER_UPDATE (TR_SECOND / TR_UPDATES_PER_SECOND) 28 29 #define TR_LOG_ERR(message, ...) fprintf(stderr, (message), ##__VA_ARGS__) 30 31 #ifdef TR_LOGLEVEL_DEBUG 32 #define TR_LOG_DBG(message, ...) printf((message), ##__VA_ARGS__) 33 #else 34 #define TR_LOG_DBG(message, ...) 35 #endif 36 37 #ifdef TR_LOGLEVEL_DEBUG_FRAME 38 #define TR_LOG_FRM(message, ...) TR_LOG_DBG((message), ##__VA_ARGS__) 39 #else 40 #define TR_LOG_FRM(message, ...) 41 #endif 42 43 enum Color 44 { 45 COLOR_GREEN, 46 COLOR_RED, 47 COLOR_BLUE, 48 COLOR_YELLOW, 49 COLOR_MAGENTA, 50 COLOR_CYAN, 51 COLOR_WHITE 52 }; 53 54 struct SDLOffscreenBuffer 55 { 56 // pixels are always 32-bits wide. Memory order: BB GG RR XX. 57 SDL_Texture *texture; 58 void *memory; 59 uint32_t width; 60 uint32_t height; 61 int32_t pitch; 62 }; 63 64 struct SDLWindowDimension 65 { 66 int32_t width; 67 int32_t height; 68 }; 69 70 struct TransformData 71 { 72 int32_t width; 73 int32_t height; 74 int32_t **distance_table; 75 int32_t **angle_table; 76 int32_t look_shift_x; 77 int32_t look_shift_y; 78 }; 79 80 static struct SDLOffscreenBuffer global_back_buffer; 81 static SDL_GameController *controller_handles[TR_MAX_CONTROLLERS]; 82 static SDL_Haptic *rumble_handles[TR_MAX_CONTROLLERS]; 83 static struct TransformData transform; 84 85 86 uint64_t 87 get_current_time_ms(void) 88 { 89 return SDL_GetTicks64(); 90 } 91 92 93 void 94 render_texture( 95 struct SDLOffscreenBuffer buffer, 96 uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH], 97 int32_t x_offset, 98 int32_t y_offset, 99 enum Color color_choice) 100 { 101 uint8_t *row = (uint8_t *)buffer.memory; 102 103 for (uint32_t y = 0; y < buffer.height; ++y) 104 { 105 uint32_t *pixel = (uint32_t *)row; 106 for (uint32_t x = 0; x < buffer.width; ++x) 107 { 108 uint8_t color = texture[ 109 (uint32_t)(y + y_offset) % TR_TEX_HEIGHT 110 ] 111 [ 112 (uint32_t)(x + x_offset) % TR_TEX_WIDTH 113 ]; 114 uint32_t red = color << 16; 115 uint32_t green = color << 8; 116 uint32_t blue = color; 117 118 switch(color_choice) 119 { 120 case COLOR_GREEN: 121 { 122 *pixel++ = green; 123 } break; 124 case COLOR_RED: 125 { 126 *pixel++ = red; 127 } break; 128 case COLOR_BLUE: 129 { 130 *pixel++ = blue; 131 } break; 132 case COLOR_YELLOW: 133 { 134 *pixel++ = red | green; 135 } break; 136 case COLOR_MAGENTA: 137 { 138 *pixel++ = red | blue; 139 } break; 140 case COLOR_CYAN: 141 { 142 *pixel++ = blue | green; 143 } break; 144 case COLOR_WHITE: 145 { 146 *pixel++ = red | green | blue; 147 } break; 148 default: 149 { 150 TR_LOG_ERR("Invalid color enum value: %d\n", color_choice); 151 *pixel++ = red | green | blue; 152 } break; 153 } 154 } 155 156 row += buffer.pitch; 157 } 158 } 159 160 161 void 162 render_tunnel( 163 struct SDLOffscreenBuffer buffer, 164 uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH], 165 int32_t rotation_offset, 166 int32_t translation_offset, 167 enum Color color_choice) 168 { 169 uint8_t *row = (uint8_t *)buffer.memory; 170 171 for (uint32_t y = 0; y < buffer.height; ++y) 172 { 173 uint32_t *pixel = (uint32_t *)row; 174 for (uint32_t x = 0; x < buffer.width; ++x) 175 { 176 uint32_t texel_y = (uint32_t)(transform.distance_table[y + transform.look_shift_y][x + transform.look_shift_x] + translation_offset) % TR_TEX_HEIGHT; 177 uint32_t texel_x = (uint32_t)(transform.angle_table[y + transform.look_shift_y][x + transform.look_shift_x] + rotation_offset) % TR_TEX_WIDTH; 178 179 assert(texel_x >= 0 && texel_x < TR_TEX_WIDTH); 180 assert(texel_y >= 0 && texel_y < TR_TEX_HEIGHT); 181 182 uint8_t texel = texture[texel_y][texel_x]; 183 184 uint32_t red = texel << 16; 185 uint32_t green = texel << 8; 186 uint32_t blue = texel; 187 188 switch(color_choice) 189 { 190 case COLOR_GREEN: 191 { 192 *pixel++ = green; 193 } break; 194 case COLOR_RED: 195 { 196 *pixel++ = red; 197 } break; 198 case COLOR_BLUE: 199 { 200 *pixel++ = blue; 201 } break; 202 case COLOR_YELLOW: 203 { 204 *pixel++ = red | green; 205 } break; 206 case COLOR_MAGENTA: 207 { 208 *pixel++ = red | blue; 209 } break; 210 case COLOR_CYAN: 211 { 212 *pixel++ = blue | green; 213 } break; 214 case COLOR_WHITE: 215 { 216 *pixel++ = red | green | blue; 217 } break; 218 default: 219 { 220 *pixel++ = red | green | blue; 221 TR_LOG_ERR("Invalid color enum value: %d\n", color_choice); 222 } break; 223 } 224 } 225 row += global_back_buffer.pitch; 226 } 227 } 228 229 230 struct SDLWindowDimension 231 sdl_get_window_dimension(SDL_Window *window) 232 { 233 int w = 0; 234 int h = 0; 235 SDL_GetWindowSize(window, &w, &h); 236 struct SDLWindowDimension result = { .width = w, .height = h }; 237 return result; 238 } 239 240 241 void 242 sdl_resize_texture(struct SDLOffscreenBuffer *buffer, SDL_Renderer *renderer, int32_t window_width, int32_t window_height) 243 { 244 if (buffer->memory) 245 { 246 free(buffer->memory); 247 } 248 249 if (buffer->texture) 250 { 251 SDL_DestroyTexture(buffer->texture); 252 } 253 254 if (transform.distance_table) 255 { 256 for (int32_t y = 0; y < transform.height; ++y) 257 { 258 free(transform.distance_table[y]); 259 } 260 free(transform.distance_table); 261 } 262 if (transform.angle_table) 263 { 264 for (int32_t y = 0; y < transform.height; ++y) 265 { 266 free(transform.angle_table[y]); 267 } 268 free(transform.angle_table); 269 } 270 271 buffer->texture = SDL_CreateTexture( 272 renderer, 273 SDL_PIXELFORMAT_ARGB8888, 274 SDL_TEXTUREACCESS_STREAMING, 275 window_width, window_height); 276 277 buffer->width = window_width; 278 buffer->height = window_height; 279 buffer->pitch = window_width * TR_BYTES_PER_PIXEL; 280 281 buffer->memory = malloc(window_width * window_height * TR_BYTES_PER_PIXEL); 282 283 transform.width = 2 * window_width; 284 transform.height = 2 * window_height; 285 transform.look_shift_x = window_width / 2; 286 transform.look_shift_y = window_height / 2; 287 transform.distance_table = malloc(transform.height * sizeof(int32_t *)); 288 transform.angle_table = malloc(transform.height * sizeof(int32_t *)); 289 290 for (int32_t y = 0; y < transform.height; ++y) 291 { 292 transform.distance_table[y] = malloc(transform.width * sizeof(int32_t)); 293 transform.angle_table[y] = malloc(transform.width * sizeof(int32_t)); 294 } 295 296 // Make distance and angle transformation tables 297 for (int32_t y = 0; y < transform.height; ++y) 298 { 299 for (int32_t x = 0; x < transform.width; ++x) 300 { 301 int32_t dist_from_center_x = x - window_width; 302 int32_t dist_from_center_y = y - window_height; 303 float dist_from_center = sqrtf((float)(dist_from_center_x * dist_from_center_x + dist_from_center_y * dist_from_center_y)); 304 float angle_from_positive_x_axis = atan2f((float)dist_from_center_y, (float)dist_from_center_x) / TR_PI32; 305 306 float ratio = 32.0f; 307 transform.distance_table[y][x] = (int32_t)(ratio * TR_TEX_HEIGHT / dist_from_center) % TR_TEX_HEIGHT; 308 transform.angle_table[y][x] = (int32_t)(0.5f * TR_TEX_WIDTH * angle_from_positive_x_axis); 309 } 310 } 311 } 312 313 314 void 315 sdl_update_window(SDL_Renderer *renderer, struct SDLOffscreenBuffer buffer) 316 { 317 if (SDL_UpdateTexture(buffer.texture, 0, buffer.memory, buffer.pitch)) 318 { 319 TR_LOG_ERR("SDL_UpdateTexture failed: %s\n", SDL_GetError()); 320 } 321 322 SDL_RenderCopy(renderer, buffer.texture, 0, 0); 323 SDL_RenderPresent(renderer); 324 } 325 326 327 bool 328 handle_event(SDL_Event *event) 329 { 330 bool should_quit = false; 331 332 switch(event->type) 333 { 334 case SDL_QUIT: 335 { 336 TR_LOG_DBG("SDL_QUIT\n"); 337 should_quit = true; 338 } break; 339 340 case SDL_WINDOWEVENT: 341 { 342 switch(event->window.event) 343 { 344 case SDL_WINDOWEVENT_SIZE_CHANGED: 345 { 346 SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); 347 SDL_Renderer *renderer = SDL_GetRenderer(window); 348 TR_LOG_DBG("SDL_WINDOWEVENT_SIZE_CHANGED (%d, %d)\n", event->window.data1, event->window.data2); 349 sdl_resize_texture(&global_back_buffer, renderer, event->window.data1, event->window.data2); 350 } break; 351 352 case SDL_WINDOWEVENT_FOCUS_GAINED: 353 { 354 TR_LOG_DBG("SDL_WINDOWEVENT_FOCUS_GAINED\n"); 355 } break; 356 357 case SDL_WINDOWEVENT_EXPOSED: 358 { 359 SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); 360 SDL_Renderer *renderer = SDL_GetRenderer(window); 361 sdl_update_window(renderer, global_back_buffer); 362 } break; 363 } 364 } break; 365 } 366 return should_quit ; 367 } 368 369 370 void 371 sdl_open_game_controllers() 372 { 373 int32_t num_joysticks = SDL_NumJoysticks(); 374 for (int32_t controller_index = 0; controller_index < num_joysticks; ++controller_index) 375 { 376 if (!SDL_IsGameController(controller_index)) 377 { 378 continue; 379 } 380 381 if (controller_index >= TR_MAX_CONTROLLERS) 382 { 383 break; 384 } 385 386 controller_handles[controller_index] = SDL_GameControllerOpen(controller_index); 387 rumble_handles[controller_index] = SDL_HapticOpen(controller_index); 388 389 if (rumble_handles[controller_index] && SDL_HapticRumbleInit(rumble_handles[controller_index]) != 0) 390 { 391 SDL_HapticClose(rumble_handles[controller_index]); 392 rumble_handles[controller_index] = 0; 393 } 394 } 395 } 396 397 398 void 399 sdl_close_game_controllers() 400 { 401 for (int32_t controller_index = 0; controller_index < TR_MAX_CONTROLLERS; ++controller_index) 402 { 403 if (controller_handles[controller_index]) 404 { 405 SDL_GameControllerClose(controller_handles[controller_index]); 406 } 407 } 408 } 409 410 411 void 412 sdl_cleanup(void) 413 { 414 TR_LOG_DBG("Cleaning up...\n"); 415 sdl_close_game_controllers(); 416 SDL_Quit(); 417 } 418 419 420 int 421 main(int argc, char *argv[]) 422 { 423 (void)argc; 424 (void)argv; 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 }