woodeneye-008.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /*
  2. * This code is public domain. Feel free to use it for any purpose!
  3. */
  4. #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
  5. #include <SDL3/SDL.h>
  6. #include <SDL3/SDL_main.h>
  7. #define MAP_BOX_SCALE 16
  8. #define MAP_BOX_EDGES_LEN (12 + MAP_BOX_SCALE * 2)
  9. #define MAX_PLAYER_COUNT 4
  10. #define CIRCLE_DRAW_SIDES 32
  11. #define CIRCLE_DRAW_SIDES_LEN (CIRCLE_DRAW_SIDES + 1)
  12. typedef struct {
  13. SDL_MouseID mouse;
  14. SDL_KeyboardID keyboard;
  15. double pos[3];
  16. double vel[3];
  17. unsigned int yaw;
  18. int pitch;
  19. float radius, height;
  20. unsigned char color[3];
  21. unsigned char wasd;
  22. } Player;
  23. typedef struct {
  24. SDL_Window *window;
  25. SDL_Renderer *renderer;
  26. int player_count;
  27. Player players[MAX_PLAYER_COUNT];
  28. float edges[MAP_BOX_EDGES_LEN][6];
  29. } AppState;
  30. static const struct {
  31. const char *key;
  32. const char *value;
  33. } extended_metadata[] = {
  34. { SDL_PROP_APP_METADATA_URL_STRING, "https://examples.libsdl.org/SDL3/demo/02-woodeneye-008/" },
  35. { SDL_PROP_APP_METADATA_CREATOR_STRING, "SDL team" },
  36. { SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Placed in the public domain" },
  37. { SDL_PROP_APP_METADATA_TYPE_STRING, "game" }
  38. };
  39. static int whoseMouse(SDL_MouseID mouse, const Player players[], int players_len)
  40. {
  41. int i;
  42. for (i = 0; i < players_len; i++) {
  43. if (players[i].mouse == mouse) return i;
  44. }
  45. return -1;
  46. }
  47. static int whoseKeyboard(SDL_KeyboardID keyboard, const Player players[], int players_len)
  48. {
  49. int i;
  50. for (i = 0; i < players_len; i++) {
  51. if (players[i].keyboard == keyboard) return i;
  52. }
  53. return -1;
  54. }
  55. static void shoot(int shooter, Player players[], int players_len)
  56. {
  57. int i, j;
  58. double x0 = players[shooter].pos[0];
  59. double y0 = players[shooter].pos[1];
  60. double z0 = players[shooter].pos[2];
  61. double bin_rad = SDL_PI_D / 2147483648.0;
  62. double yaw_rad = bin_rad * players[shooter].yaw;
  63. double pitch_rad = bin_rad * players[shooter].pitch;
  64. double cos_yaw = SDL_cos( yaw_rad);
  65. double sin_yaw = SDL_sin( yaw_rad);
  66. double cos_pitch = SDL_cos(pitch_rad);
  67. double sin_pitch = SDL_sin(pitch_rad);
  68. double vx = -sin_yaw*cos_pitch;
  69. double vy = sin_pitch;
  70. double vz = -cos_yaw*cos_pitch;
  71. for (i = 0; i < players_len; i++) {
  72. if (i == shooter) continue;
  73. Player *target = &(players[i]);
  74. int hit = 0;
  75. for (j = 0; j < 2; j++) {
  76. double r = target->radius;
  77. double h = target->height;
  78. double dx = target->pos[0] - x0;
  79. double dy = target->pos[1] - y0 + (j == 0 ? 0 : r - h);
  80. double dz = target->pos[2] - z0;
  81. double vd = vx*dx + vy*dy + vz*dz;
  82. double dd = dx*dx + dy*dy + dz*dz;
  83. double vv = vx*vx + vy*vy + vz*vz;
  84. double rr = r * r;
  85. if (vd < 0) continue;
  86. if (vd * vd >= vv * (dd - rr)) hit += 1;
  87. }
  88. if (hit) {
  89. target->pos[0] = (double)(MAP_BOX_SCALE * (SDL_rand(256) - 128)) / 256;
  90. target->pos[1] = (double)(MAP_BOX_SCALE * (SDL_rand(256) - 128)) / 256;
  91. target->pos[2] = (double)(MAP_BOX_SCALE * (SDL_rand(256) - 128)) / 256;
  92. }
  93. }
  94. }
  95. static void update(Player *players, int players_len, Uint64 dt_ns)
  96. {
  97. int i;
  98. for (i = 0; i < players_len; i++) {
  99. Player *player = &players[i];
  100. double rate = 6.0;
  101. double time = (double)dt_ns * 1e-9;
  102. double drag = SDL_exp(-time * rate);
  103. double diff = 1.0 - drag;
  104. double mult = 60.0;
  105. double grav = 25.0;
  106. double yaw = (double)player->yaw;
  107. double rad = yaw * SDL_PI_D / 2147483648.0;
  108. double cos = SDL_cos(rad);
  109. double sin = SDL_sin(rad);
  110. unsigned char wasd = player->wasd;
  111. double dirX = (wasd & 8 ? 1.0 : 0.0) - (wasd & 2 ? 1.0 : 0.0);
  112. double dirZ = (wasd & 4 ? 1.0 : 0.0) - (wasd & 1 ? 1.0 : 0.0);
  113. double norm = dirX * dirX + dirZ * dirZ;
  114. double accX = mult * (norm == 0 ? 0 : ( cos*dirX + sin*dirZ) / SDL_sqrt(norm));
  115. double accZ = mult * (norm == 0 ? 0 : (-sin*dirX + cos*dirZ) / SDL_sqrt(norm));
  116. double velX = player->vel[0];
  117. double velY = player->vel[1];
  118. double velZ = player->vel[2];
  119. player->vel[0] -= velX * diff;
  120. player->vel[1] -= grav * time;
  121. player->vel[2] -= velZ * diff;
  122. player->vel[0] += diff * accX / rate;
  123. player->vel[2] += diff * accZ / rate;
  124. player->pos[0] += (time - diff/rate) * accX / rate + diff * velX / rate;
  125. player->pos[1] += -0.5 * grav * time * time + velY * time;
  126. player->pos[2] += (time - diff/rate) * accZ / rate + diff * velZ / rate;
  127. double scale = (double)MAP_BOX_SCALE;
  128. double bound = scale - player->radius;
  129. double posX = SDL_max(SDL_min(bound, player->pos[0]), -bound);
  130. double posY = SDL_max(SDL_min(bound, player->pos[1]), player->height - scale);
  131. double posZ = SDL_max(SDL_min(bound, player->pos[2]), -bound);
  132. if (player->pos[0] != posX) player->vel[0] = 0;
  133. if (player->pos[1] != posY) player->vel[1] = (wasd & 16) ? 8.4375 : 0;
  134. if (player->pos[2] != posZ) player->vel[2] = 0;
  135. player->pos[0] = posX;
  136. player->pos[1] = posY;
  137. player->pos[2] = posZ;
  138. }
  139. }
  140. static void drawCircle(SDL_Renderer *renderer, float r, float x, float y)
  141. {
  142. float ang;
  143. SDL_FPoint points[CIRCLE_DRAW_SIDES_LEN];
  144. int i;
  145. for (i = 0; i < CIRCLE_DRAW_SIDES_LEN; i++) {
  146. ang = 2.0f * SDL_PI_F * (float)i / (float)CIRCLE_DRAW_SIDES;
  147. points[i].x = x + r * SDL_cosf(ang);
  148. points[i].y = y + r * SDL_sinf(ang);
  149. }
  150. SDL_RenderLines(renderer, (const SDL_FPoint*)&points, CIRCLE_DRAW_SIDES_LEN);
  151. }
  152. static void drawClippedSegment(
  153. SDL_Renderer *renderer,
  154. float ax, float ay, float az,
  155. float bx, float by, float bz,
  156. float x, float y, float z, float w)
  157. {
  158. if (az >= -w && bz >= -w) return;
  159. float dx = ax - bx;
  160. float dy = ay - by;
  161. if (az > -w) {
  162. float t = (-w - bz) / (az - bz);
  163. ax = bx + dx * t;
  164. ay = by + dy * t;
  165. az = -w;
  166. } else if (bz > -w) {
  167. float t = (-w - az) / (bz - az);
  168. bx = ax - dx * t;
  169. by = ay - dy * t;
  170. bz = -w;
  171. }
  172. ax = -z * ax / az;
  173. ay = -z * ay / az;
  174. bx = -z * bx / bz;
  175. by = -z * by / bz;
  176. SDL_RenderLine(renderer, x + ax, y - ay, x + bx, y - by);
  177. }
  178. static char debug_string[32];
  179. static void draw(SDL_Renderer *renderer, const float (*edges)[6], const Player players[], int players_len)
  180. {
  181. int w, h, i, j, k;
  182. if (!SDL_GetRenderOutputSize(renderer, &w, &h)) {
  183. return;
  184. }
  185. SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
  186. SDL_RenderClear(renderer);
  187. if (players_len > 0) {
  188. float wf = (float)w;
  189. float hf = (float)h;
  190. int part_hor = players_len > 2 ? 2 : 1;
  191. int part_ver = players_len > 1 ? 2 : 1;
  192. float size_hor = wf / ((float)part_hor);
  193. float size_ver = hf / ((float)part_ver);
  194. for (i = 0; i < players_len; i++) {
  195. const Player *player = &players[i];
  196. float mod_x = (float)(i % part_hor);
  197. float mod_y = (float)(i / part_hor);
  198. float hor_origin = (mod_x + 0.5f) * size_hor;
  199. float ver_origin = (mod_y + 0.5f) * size_ver;
  200. float cam_origin = (float)(0.5 * SDL_sqrt(size_hor * size_hor + size_ver * size_ver));
  201. float hor_offset = mod_x * size_hor;
  202. float ver_offset = mod_y * size_ver;
  203. SDL_Rect rect;
  204. rect.x = (int)hor_offset;
  205. rect.y = (int)ver_offset;
  206. rect.w = (int)size_hor;
  207. rect.h = (int)size_ver;
  208. SDL_SetRenderClipRect(renderer, &rect);
  209. double x0 = player->pos[0];
  210. double y0 = player->pos[1];
  211. double z0 = player->pos[2];
  212. double bin_rad = SDL_PI_D / 2147483648.0;
  213. double yaw_rad = bin_rad * player->yaw;
  214. double pitch_rad = bin_rad * player->pitch;
  215. double cos_yaw = SDL_cos( yaw_rad);
  216. double sin_yaw = SDL_sin( yaw_rad);
  217. double cos_pitch = SDL_cos(pitch_rad);
  218. double sin_pitch = SDL_sin(pitch_rad);
  219. double mat[9] = {
  220. cos_yaw , 0, -sin_yaw ,
  221. sin_yaw*sin_pitch, cos_pitch, cos_yaw*sin_pitch,
  222. sin_yaw*cos_pitch, -sin_pitch, cos_yaw*cos_pitch
  223. };
  224. SDL_SetRenderDrawColor(renderer, 64, 64, 64, 255);
  225. for (k = 0; k < MAP_BOX_EDGES_LEN; k++) {
  226. const float *line = edges[k];
  227. float ax = (float)(mat[0] * (line[0] - x0) + mat[1] * (line[1] - y0) + mat[2] * (line[2] - z0));
  228. float ay = (float)(mat[3] * (line[0] - x0) + mat[4] * (line[1] - y0) + mat[5] * (line[2] - z0));
  229. float az = (float)(mat[6] * (line[0] - x0) + mat[7] * (line[1] - y0) + mat[8] * (line[2] - z0));
  230. float bx = (float)(mat[0] * (line[3] - x0) + mat[1] * (line[4] - y0) + mat[2] * (line[5] - z0));
  231. float by = (float)(mat[3] * (line[3] - x0) + mat[4] * (line[4] - y0) + mat[5] * (line[5] - z0));
  232. float bz = (float)(mat[6] * (line[3] - x0) + mat[7] * (line[4] - y0) + mat[8] * (line[5] - z0));
  233. drawClippedSegment(renderer, ax, ay, az, bx, by, bz, hor_origin, ver_origin, cam_origin, 1);
  234. }
  235. for (j = 0; j < players_len; j++) {
  236. if (i == j) continue;
  237. const Player *target = &players[j];
  238. SDL_SetRenderDrawColor(renderer, target->color[0], target->color[1], target->color[2], 255);
  239. for (k = 0; k < 2; k++) {
  240. double rx = target->pos[0] - player->pos[0];
  241. double ry = target->pos[1] - player->pos[1] + (target->radius - target->height) * (float)k;
  242. double rz = target->pos[2] - player->pos[2];
  243. double dx = mat[0] * rx + mat[1] * ry + mat[2] * rz;
  244. double dy = mat[3] * rx + mat[4] * ry + mat[5] * rz;
  245. double dz = mat[6] * rx + mat[7] * ry + mat[8] * rz;
  246. double r_eff = target->radius * cam_origin / dz;
  247. if (!(dz < 0)) continue;
  248. drawCircle(renderer, (float)(r_eff), (float)(hor_origin - cam_origin*dx/dz), (float)(ver_origin + cam_origin*dy/dz));
  249. }
  250. }
  251. SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
  252. SDL_RenderLine(renderer, hor_origin, ver_origin-10, hor_origin, ver_origin+10);
  253. SDL_RenderLine(renderer, hor_origin-10, ver_origin, hor_origin+10, ver_origin);
  254. }
  255. }
  256. SDL_SetRenderClipRect(renderer, 0);
  257. SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
  258. SDL_RenderDebugText(renderer, 0, 0, debug_string);
  259. SDL_RenderPresent(renderer);
  260. }
  261. static void initPlayers(Player *players, int len)
  262. {
  263. int i;
  264. for (i = 0; i < len; i++) {
  265. players[i].pos[0] = 8.0 * (i & 1 ? -1.0 : 1.0);
  266. players[i].pos[1] = 0;
  267. players[i].pos[2] = 8.0 * (i & 1 ? -1.0 : 1.0) * (i & 2 ? -1.0 : 1.0);
  268. players[i].vel[0] = 0;
  269. players[i].vel[1] = 0;
  270. players[i].vel[2] = 0;
  271. players[i].yaw = 0x20000000 + (i & 1 ? 0x80000000 : 0) + (i & 2 ? 0x40000000 : 0);
  272. players[i].pitch = -0x08000000;
  273. players[i].radius = 0.5f;
  274. players[i].height = 1.5f;
  275. players[i].wasd = 0;
  276. players[i].mouse = 0;
  277. players[i].keyboard = 0;
  278. players[i].color[0] = (1 << (i / 2)) & 2 ? 0 : 0xff;
  279. players[i].color[1] = (1 << (i / 2)) & 1 ? 0 : 0xff;
  280. players[i].color[2] = (1 << (i / 2)) & 4 ? 0 : 0xff;
  281. players[i].color[0] = (i & 1) ? players[i].color[0] : ~players[i].color[0];
  282. players[i].color[1] = (i & 1) ? players[i].color[1] : ~players[i].color[1];
  283. players[i].color[2] = (i & 1) ? players[i].color[2] : ~players[i].color[2];
  284. }
  285. }
  286. static void initEdges(int scale, float (*edges)[6], int edges_len)
  287. {
  288. int i, j;
  289. const float r = (float)scale;
  290. const int map[24] = {
  291. 0,1 , 1,3 , 3,2 , 2,0 ,
  292. 7,6 , 6,4 , 4,5 , 5,7 ,
  293. 6,2 , 3,7 , 0,4 , 5,1
  294. };
  295. for(i = 0; i < 12; i++) {
  296. for (j = 0; j < 3; j++) {
  297. edges[i][j+0] = (map[i*2+0] & (1 << j) ? r : -r);
  298. edges[i][j+3] = (map[i*2+1] & (1 << j) ? r : -r);
  299. }
  300. }
  301. for(i = 0; i < scale; i++) {
  302. float d = (float)(i * 2);
  303. for (j = 0; j < 2; j++) {
  304. edges[i+12][3*j+0] = j ? r : -r;
  305. edges[i+12][3*j+1] = -r;
  306. edges[i+12][3*j+2] = d-r;
  307. edges[i+12+scale][3*j+0] = d-r;
  308. edges[i+12+scale][3*j+1] = -r;
  309. edges[i+12+scale][3*j+2] = j ? r : -r;
  310. }
  311. }
  312. }
  313. SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
  314. {
  315. if (!SDL_SetAppMetadata("Example splitscreen shooter game", "1.0", "com.example.woodeneye-008")) {
  316. return SDL_APP_FAILURE;
  317. }
  318. int i;
  319. for (i = 0; i < SDL_arraysize(extended_metadata); i++) {
  320. if (!SDL_SetAppMetadataProperty(extended_metadata[i].key, extended_metadata[i].value)) {
  321. return SDL_APP_FAILURE;
  322. }
  323. }
  324. AppState *as = SDL_calloc(1, sizeof(AppState));
  325. if (!as) {
  326. return SDL_APP_FAILURE;
  327. } else {
  328. *appstate = as;
  329. }
  330. if (!SDL_Init(SDL_INIT_VIDEO)) {
  331. return SDL_APP_FAILURE;
  332. }
  333. if (!SDL_CreateWindowAndRenderer("examples/demo/woodeneye-008", 640, 480, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer)) {
  334. return SDL_APP_FAILURE;
  335. }
  336. as->player_count = 1;
  337. initPlayers(as->players, MAX_PLAYER_COUNT);
  338. initEdges(MAP_BOX_SCALE, as->edges, MAP_BOX_EDGES_LEN);
  339. debug_string[0] = 0;
  340. SDL_SetRenderVSync(as->renderer, false);
  341. SDL_SetWindowRelativeMouseMode(as->window, true);
  342. SDL_SetHintWithPriority(SDL_HINT_WINDOWS_RAW_KEYBOARD, "1", SDL_HINT_OVERRIDE);
  343. return SDL_APP_CONTINUE;
  344. }
  345. SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
  346. {
  347. AppState *as = appstate;
  348. Player *players = as->players;
  349. int player_count = as->player_count;
  350. int i;
  351. switch (event->type) {
  352. case SDL_EVENT_QUIT:
  353. return SDL_APP_SUCCESS;
  354. break;
  355. case SDL_EVENT_MOUSE_REMOVED:
  356. for (i = 0; i < player_count; i++) {
  357. if (players[i].mouse == event->mdevice.which) {
  358. players[i].mouse = 0;
  359. }
  360. }
  361. break;
  362. case SDL_EVENT_KEYBOARD_REMOVED:
  363. for (i = 0; i < player_count; i++) {
  364. if (players[i].keyboard == event->kdevice.which) {
  365. players[i].keyboard = 0;
  366. }
  367. }
  368. break;
  369. case SDL_EVENT_MOUSE_MOTION: {
  370. SDL_MouseID id = event->motion.which;
  371. int index = whoseMouse(id, players, player_count);
  372. if (index >= 0) {
  373. players[index].yaw -= ((int)event->motion.xrel) * 0x00080000;
  374. players[index].pitch = SDL_max(-0x40000000, SDL_min(0x40000000, players[index].pitch - ((int)event->motion.yrel) * 0x00080000));
  375. } else if (id) {
  376. for (i = 0; i < MAX_PLAYER_COUNT; i++) {
  377. if (players[i].mouse == 0) {
  378. players[i].mouse = id;
  379. as->player_count = SDL_max(as->player_count, i + 1);
  380. break;
  381. }
  382. }
  383. }
  384. break;
  385. }
  386. case SDL_EVENT_MOUSE_BUTTON_DOWN: {
  387. SDL_MouseID id = event->button.which;
  388. int index = whoseMouse(id, players, player_count);
  389. if (index >= 0) {
  390. shoot(index, players, player_count);
  391. }
  392. break;
  393. }
  394. case SDL_EVENT_KEY_DOWN: {
  395. SDL_Keycode sym = event->key.key;
  396. SDL_KeyboardID id = event->key.which;
  397. int index = whoseKeyboard(id, players, player_count);
  398. if (index >= 0) {
  399. if (sym == SDLK_W) players[index].wasd |= 1;
  400. if (sym == SDLK_A) players[index].wasd |= 2;
  401. if (sym == SDLK_S) players[index].wasd |= 4;
  402. if (sym == SDLK_D) players[index].wasd |= 8;
  403. if (sym == SDLK_SPACE) players[index].wasd |= 16;
  404. } else if (id) {
  405. for (i = 0; i < MAX_PLAYER_COUNT; i++) {
  406. if (players[i].keyboard == 0) {
  407. players[i].keyboard = id;
  408. as->player_count = SDL_max(as->player_count, i + 1);
  409. break;
  410. }
  411. }
  412. }
  413. break;
  414. }
  415. case SDL_EVENT_KEY_UP: {
  416. SDL_Keycode sym = event->key.key;
  417. SDL_KeyboardID id = event->key.which;
  418. if (sym == SDLK_ESCAPE) return SDL_APP_SUCCESS;
  419. int index = whoseKeyboard(id, players, player_count);
  420. if (index >= 0) {
  421. if (sym == SDLK_W) players[index].wasd &= 30;
  422. if (sym == SDLK_A) players[index].wasd &= 29;
  423. if (sym == SDLK_S) players[index].wasd &= 27;
  424. if (sym == SDLK_D) players[index].wasd &= 23;
  425. if (sym == SDLK_SPACE) players[index].wasd &= 15;
  426. }
  427. break;
  428. }
  429. }
  430. return SDL_APP_CONTINUE;
  431. }
  432. SDL_AppResult SDL_AppIterate(void *appstate)
  433. {
  434. AppState *as = appstate;
  435. static Uint64 accu = 0;
  436. static Uint64 last = 0;
  437. static Uint64 past = 0;
  438. Uint64 now = SDL_GetTicksNS();
  439. Uint64 dt_ns = now - past;
  440. update(as->players, as->player_count, dt_ns);
  441. draw(as->renderer, (const float (*)[6])as->edges, as->players, as->player_count);
  442. if (now - last > 999999999) {
  443. last = now;
  444. SDL_snprintf(debug_string, sizeof(debug_string), "%" SDL_PRIu64 " fps", accu);
  445. accu = 0;
  446. }
  447. past = now;
  448. accu += 1;
  449. Uint64 elapsed = SDL_GetTicksNS() - now;
  450. if (elapsed < 999999) {
  451. SDL_DelayNS(999999 - elapsed);
  452. }
  453. return SDL_APP_CONTINUE;
  454. }
  455. void SDL_AppQuit(void *appstate, SDL_AppResult result)
  456. {
  457. SDL_free(appstate); // just free the memory, SDL will clean up the window/renderer for us.
  458. }