RAYCAST.C 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /*
  2. * Raycaster, based on lodev engine tutorial
  3. * Plan:
  4. * - Improve collision detection:
  5. * - During movement function,
  6. * - Check if new pos has distance from possible wall hor and ver
  7. * - Make rendering variables global with prefix
  8. * - Convert rendering function to assembly
  9. * - Add sprites (as this is done after walls)
  10. */
  11. #define word char
  12. #include "LIB/MATH.C"
  13. #include "LIB/STDLIB.C"
  14. #include "LIB/SYS.C"
  15. #include "LIB/GFX.C"
  16. #include "LIB/FP.C"
  17. // Note: these are also hardcoded in the render assembly, so update there as well!
  18. #define FB_ADDR 0xD00000
  19. #define screenWidth 320
  20. #define screenHeight 240
  21. #define texWidth 64
  22. #define texHeight 64
  23. // Map
  24. #define mapWidth 24
  25. #define mapHeight 24
  26. // Input
  27. #define BTN_LEFT 256
  28. #define BTN_RIGHT 257
  29. #define BTN_UP 258
  30. #define BTN_DOWN 259
  31. // Colors
  32. #define COLOR_RED 224
  33. #define COLOR_DARK_RED 96
  34. #define COLOR_GREEN 28
  35. #define COLOR_DARK_GREEN 12
  36. #define COLOR_BLUE 3
  37. #define COLOR_DARK_BLUE 2
  38. #define COLOR_WHITE 0xFF
  39. #define COLOR_GREY 0xB6
  40. #define COLOR_YELLOW 0xFC
  41. #define COLOR_DARK_YELLOW 0x90
  42. // Data
  43. /*
  44. - LUTdirX
  45. - LUTdirY
  46. - LUTplaneX
  47. - LUTplaney
  48. - texture
  49. */
  50. #include "DATA/RAYDAT.C"
  51. // Framebuffer. fb[Y][X] (bottom right is [239][319])
  52. char (*fb)[screenWidth] = (char (*)[screenWidth])FB_ADDR;
  53. word worldMap[mapWidth][mapHeight] = {
  54. {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, 7, 7, 7, 7, 7, 7, 7},
  55. {4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 7},
  56. {4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7},
  57. {4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7},
  58. {4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 7},
  59. {4, 0, 4, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7, 7, 0, 7, 7, 7, 7, 7},
  60. {4, 0, 5, 0, 0, 0, 0, 2, 0, 1, 0, 1, 0, 1, 0, 2, 7, 0, 0, 0, 7, 7, 7, 1},
  61. {4, 0, 6, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0, 0, 8},
  62. {4, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 1},
  63. {4, 0, 8, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 5, 7, 0, 0, 0, 0, 0, 0, 8},
  64. {4, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 5, 7, 0, 0, 0, 7, 7, 7, 1},
  65. {4, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 0, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 1},
  66. {6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6},
  67. {8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4},
  68. {6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6},
  69. {4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 6, 0, 6, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3},
  70. {4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 0, 6, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2},
  71. {4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 2, 0, 0, 5, 0, 0, 2, 0, 0, 0, 2},
  72. {4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 0, 6, 2, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2},
  73. {4, 0, 6, 0, 6, 0, 0, 0, 0, 4, 6, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 2},
  74. {4, 0, 0, 5, 0, 0, 0, 0, 0, 4, 6, 0, 6, 2, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2},
  75. {4, 0, 6, 0, 6, 0, 0, 0, 0, 4, 6, 0, 6, 2, 0, 0, 5, 0, 0, 2, 0, 0, 0, 2},
  76. {4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 0, 6, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2},
  77. {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3}};
  78. // Global variables, so render function can access it
  79. word drawStart = 0;
  80. word drawEnd = 0;
  81. word texNum = 0;
  82. word side = 0; // was a NorthSouth or a EastWest wall hit?
  83. // Render vertical line in pixel plane with textures
  84. // INPUT:
  85. // r1 first pixel addr VRAM addr of first pixel in line (top pixel)
  86. // r2 (a2r) drawStart Starting pixel of wall
  87. // r3 (a2r) drawEnd Ending pixel of wall
  88. // r4 texPos Starting texture coordinate
  89. // r5 step How much to increase the texture coordinate per screen pixel
  90. // r6 texX X coordinate on the texture
  91. // r7 x X position of line to render
  92. // r8 current FB pos Current framebuffer position in VRAM (top to bottom)
  93. // r9 end loop FB pos Last framebuffer position in VRAM of current loop
  94. // r10 texY Y coordinate on the texture
  95. // r11 gp Used as temp reg in calculations
  96. // r12 texture[texNum] Texture array of texture to draw
  97. // r13 color Pixel color
  98. // r14 ceil or floor col Ceiling or floor color
  99. // r15 side North South or East West wall side
  100. void RAYFX_drawTextureVertLine(fixed_point_t texPos, fixed_point_t step, word texX, word x)
  101. {
  102. // reg 4 5 6 7 (args) and 2 3 (retval) are safe
  103. asm(
  104. "; backup registers\n"
  105. "push r1\n"
  106. "push r8\n"
  107. "push r9\n"
  108. "push r10\n"
  109. "push r11\n"
  110. "push r12\n"
  111. "push r13\n"
  112. "push r14\n"
  113. "push r15\n"
  114. );
  115. // setup registers
  116. asm(
  117. "addr2reg drawStart r2 ; r2 = drawStart addr\n"
  118. "read 0 r2 r2 ; r2 = drawStart value\n"
  119. "addr2reg drawEnd r3 ; r3 = drawEnd addr\n"
  120. "read 0 r3 r3 ; r3 = drawEnd value\n"
  121. "load32 0xD00000 r1 ; r1 = framebuffer addr\n"
  122. "add r1 r7 r1 ; r1 = first pixel in line (fb+x)\n"
  123. "or r0 r1 r8 ; r8 = current pixel\n"
  124. "multu r2 320 r9 ; r9 = drawStart VRAM offset\n"
  125. "add r9 r1 r9 ; r9 = last FB pos of before wall\n"
  126. "addr2reg texNum r12 ; r12 = texNum addr\n"
  127. "read 0 r12 r12 ; r12 = texNum value\n"
  128. "multu r12 4096 r12 ; r12 = texture offset (64*64 per texture)\n"
  129. "addr2reg texture r11 ; r11 = texture array\n"
  130. "add r12 r11 r12 ; r12 = texture[texNum]\n"
  131. "load32 0x0082F0 r14 ; r14 = ceiling color\n"
  132. "addr2reg side r15 ; r15 = side addr\n"
  133. "read 0 r15 r15 ; r15 = side value\n"
  134. "; draw until start\n"
  135. "RAYFX_drawVlineLoopCeiling:\n"
  136. " write 0 r8 r14 ; write ceiling pixel\n"
  137. " add r8 320 r8 ; go to next line pixel\n"
  138. " bge r8 r9 2 ; keep looping until reached wall\n"
  139. " jump RAYFX_drawVlineLoopCeiling\n"
  140. "multu r3 320 r9 ; r9 = drawEnd VRAM offset\n"
  141. "add r9 r1 r9 ; r9 = last FB pos of wall\n"
  142. "load32 8355711 r2 ; r2 = mask for darken color\n"
  143. "; draw until floor\n"
  144. "RAYFX_drawVlineLoopWall:\n"
  145. " shiftrs r4 16 r11 ; r11 = texY = FPtoInt(texPos)\n"
  146. " and r11 63 r11 ; r11 = r11 & (texHeight-1)\n"
  147. " add r4 r5 r4 ; texPos += step\n"
  148. " multu r11 64 r11 ; r11 = texHeight * texY \n"
  149. " add r11 r6 r11 ; r11 += texX\n"
  150. " add r11 r12 r13 ; r13 = addr of color in texture array\n"
  151. " read 0 r13 r13 ; r13 = pixel color\n"
  152. " beq r15 r0 3 ; skip next two lines if not side of wall is hit\n"
  153. " shiftrs r13 1 r13 ; r13 >> 1\n"
  154. " and r13 r2 r13 ; r13 & darken color mask\n"
  155. " write 0 r8 r13 ; write texture pixel\n"
  156. " add r8 320 r8 ; go to next line pixel\n"
  157. " bge r8 r9 2 ; keep looping until reached floor\n"
  158. " jump RAYFX_drawVlineLoopWall\n"
  159. "load 239 r9 ; r9 = last y position\n"
  160. "multu r9 320 r9 ; r9 = screen end VRAM offset\n"
  161. "add r9 r1 r9 ; r9 = last FB pos of line\n"
  162. "load32 0x9E9E9E r14 ; r14 = floor color\n"
  163. "; draw until end of screen\n"
  164. "RAYFX_drawVlineLoopFloor:\n"
  165. " write 0 r8 r14 ; write floor pixel\n"
  166. " add r8 320 r8 ; go to next line pixel\n"
  167. " bgt r8 r9 2 ; keep looping until reached end of screen\n"
  168. " jump RAYFX_drawVlineLoopFloor\n"
  169. );
  170. asm(
  171. "; restore registers\n"
  172. "pop r15\n"
  173. "pop r14\n"
  174. "pop r13\n"
  175. "pop r12\n"
  176. "pop r11\n"
  177. "pop r10\n"
  178. "pop r9\n"
  179. "pop r8\n"
  180. "pop r1\n"
  181. );
  182. }
  183. // x and y start position
  184. fixed_point_t RAY_posX;
  185. fixed_point_t RAY_posY;
  186. // initial direction vector
  187. fixed_point_t RAY_dirX;
  188. fixed_point_t RAY_dirY;
  189. // the 2d raycaster version of camera plane
  190. fixed_point_t RAY_planeX;
  191. fixed_point_t RAY_planeY;
  192. word RAY_linex; // current vertical line being rendered (x of screen)
  193. int main() {
  194. // clear screen from text
  195. GFX_clearWindowtileTable();
  196. GFX_clearWindowpaletteTable();
  197. GFX_clearBGtileTable();
  198. GFX_clearBGpaletteTable();
  199. // x and y start position
  200. RAY_posX = FP_intToFP(15);
  201. RAY_posY = FP_StringToFP("11.5");
  202. // initial direction vector
  203. RAY_dirX = LUTdirX[0];
  204. RAY_dirY = LUTdirY[0];
  205. // the 2d raycaster version of camera plane
  206. RAY_planeX = LUTplaneX[0];
  207. RAY_planeY = LUTplaneY[0];
  208. // rotation angle (loops at 360)
  209. word rotationAngle = 0;
  210. word rotationSpeed = 5; // degrees per frame
  211. fixed_point_t moveSpeed = FP_StringToFP("0.15");
  212. fixed_point_t movePadding = FP_StringToFP("0.3");
  213. word quitGame = 0;
  214. while (!quitGame) {
  215. for (RAY_linex = 0; RAY_linex < screenWidth; RAY_linex++)
  216. {
  217. // calculate ray position and direction
  218. fixed_point_t cameraX =
  219. FP_Div(FP_intToFP(RAY_linex << 1), FP_intToFP(screenWidth)) -
  220. FP_intToFP(1); // x-coordinate in camera space
  221. fixed_point_t rayDirX = RAY_dirX + FP_Mult(RAY_planeX, cameraX);
  222. fixed_point_t rayDirY = RAY_dirY + FP_Mult(RAY_planeY, cameraX);
  223. // which box of the map we are in
  224. word mapX = FP_FPtoInt(RAY_posX);
  225. word mapY = FP_FPtoInt(RAY_posY);
  226. // length of ray from current position to next x or y-side
  227. fixed_point_t sideDistX;
  228. fixed_point_t sideDistY;
  229. // length of ray from one x or y-side to next x or y-side
  230. // these are derived as:
  231. // deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
  232. // deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
  233. // which can be simplified to abs(|rayDir| / rayDirX) and abs(|rayDir| /
  234. // rayDirY) where |rayDir| is the length of the vector (rayDirX, rayDirY).
  235. // Its length, unlike (RAY_dirX, RAY_dirY) is not 1, however this does not matter,
  236. // only the ratio between deltaDistX and deltaDistY matters, due to the
  237. // way the DDA stepping further below works. So the values can be computed
  238. // as below.
  239. // Division through zero is prevented by setting the result to a very
  240. // high value
  241. fixed_point_t deltaDistX =
  242. (rayDirX == 0) ? 1 << 30 : MATH_abs(FP_Div(FP_intToFP(1), rayDirX));
  243. fixed_point_t deltaDistY =
  244. (rayDirY == 0) ? 1 << 30 : MATH_abs(FP_Div(FP_intToFP(1), rayDirY));
  245. fixed_point_t perpWallDist;
  246. // what direction to step in x or y-direction (either +1 or -1)
  247. word stepX;
  248. word stepY;
  249. word hit = 0; // was there a wall hit?
  250. // calculate step and initial sideDist
  251. if (rayDirX < 0) {
  252. stepX = -1;
  253. sideDistX = FP_Mult((RAY_posX - FP_intToFP(mapX)), deltaDistX);
  254. } else {
  255. stepX = 1;
  256. sideDistX = FP_Mult((FP_intToFP(mapX + 1) - RAY_posX), deltaDistX);
  257. }
  258. if (rayDirY < 0) {
  259. stepY = -1;
  260. sideDistY = FP_Mult((RAY_posY - FP_intToFP(mapY)), deltaDistY);
  261. } else {
  262. stepY = 1;
  263. sideDistY = FP_Mult((FP_intToFP(mapY + 1) - RAY_posY), deltaDistY);
  264. }
  265. // perform DDA
  266. while (hit == 0) {
  267. // jump to next map square, either in x-direction, or in y-direction
  268. if (sideDistX < sideDistY) {
  269. sideDistX += deltaDistX;
  270. mapX += stepX;
  271. side = 0;
  272. } else {
  273. sideDistY += deltaDistY;
  274. mapY += stepY;
  275. side = 1;
  276. }
  277. // Check if ray has hit a wall
  278. if (worldMap[mapX][mapY] > 0) hit = 1;
  279. }
  280. // Calculate distance projected on camera direction. This is the shortest
  281. // distance from the point where the wall is hit to the camera plane.
  282. // Euclidean to center camera point would give fisheye effect! This can be
  283. // computed as (mapX - RAY_posX + (1 - stepX) / 2) / rayDirX for side == 0, or
  284. // same formula with Y for size == 1, but can be simplified to the code
  285. // below thanks to how sideDist and deltaDist are computed: because they
  286. // were left scaled to |rayDir|. sideDist is the entire length of the ray
  287. // above after the multiple steps, but we subtract deltaDist once because
  288. // one step more into the wall was taken above.
  289. if (side == 0)
  290. perpWallDist = (sideDistX - deltaDistX);
  291. else
  292. perpWallDist = (sideDistY - deltaDistY);
  293. // Calculate height of line to draw on screen
  294. word lineHeight =
  295. FP_FPtoInt(FP_Div(FP_intToFP(screenHeight), perpWallDist));
  296. // calculate lowest and highest pixel to fill in current stripe
  297. drawStart = -(lineHeight >> 1) + (screenHeight >> 1);
  298. if (drawStart < 0) drawStart = 0;
  299. drawEnd = (lineHeight >> 1) + (screenHeight >> 1);
  300. if (drawEnd >= screenHeight) drawEnd = screenHeight - 1;
  301. // texturing calculations
  302. texNum = worldMap[mapX][mapY] -
  303. 1; // 1 subtracted from it so that texture 0 can be used!
  304. // calculate value of wallX
  305. fixed_point_t wallX; // where exactly the wall was hit
  306. if (side == 0)
  307. wallX = RAY_posY + FP_Mult(perpWallDist, rayDirY);
  308. else
  309. wallX = RAY_posX + FP_Mult(perpWallDist, rayDirX);
  310. word floormask = 0xFFFF;
  311. wallX &= floormask; // wallX-=floor(wallX)
  312. // x coordinate on the texture
  313. word texX = FP_FPtoInt(FP_Mult(wallX, FP_intToFP(texWidth)));
  314. if (side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
  315. if (side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
  316. // How much to increase the texture coordinate per screen pixel
  317. fixed_point_t step =
  318. FP_Div(FP_intToFP(texHeight), FP_intToFP(lineHeight));
  319. // Starting texture coordinate
  320. fixed_point_t texPos = FP_Mult(
  321. FP_intToFP(drawStart - (screenHeight >> 1) + (lineHeight >> 1)),
  322. step);
  323. // TMP fix for first line not rendering correctly and prevent crash when
  324. // start > screen end
  325. if (RAY_linex > 0 && drawStart < screenHeight) {
  326. RAYFX_drawTextureVertLine(texPos, step, texX, RAY_linex);
  327. }
  328. }
  329. // check which button is held
  330. if (BDOS_USBkeyHeld(BTN_LEFT)) {
  331. // both camera direction and camera plane must be rotated
  332. rotationAngle -= rotationSpeed;
  333. if (rotationAngle < 0) {
  334. rotationAngle += 360;
  335. }
  336. RAY_dirX = LUTdirX[rotationAngle];
  337. RAY_dirY = LUTdirY[rotationAngle];
  338. RAY_planeX = LUTplaneX[rotationAngle];
  339. RAY_planeY = LUTplaneY[rotationAngle];
  340. } else if (BDOS_USBkeyHeld(BTN_RIGHT)) {
  341. // both camera direction and camera plane must be rotated
  342. rotationAngle += rotationSpeed;
  343. if (rotationAngle >= 360) {
  344. rotationAngle -= 360;
  345. }
  346. RAY_dirX = LUTdirX[rotationAngle];
  347. RAY_dirY = LUTdirY[rotationAngle];
  348. RAY_planeX = LUTplaneX[rotationAngle];
  349. RAY_planeY = LUTplaneY[rotationAngle];
  350. }
  351. if (BDOS_USBkeyHeld(BTN_UP)) {
  352. word worldMapX = FP_FPtoInt(RAY_posX + FP_Mult(RAY_dirX, moveSpeed + movePadding));
  353. word worldMapY = FP_FPtoInt(RAY_posY);
  354. if (worldMap[worldMapX][worldMapY] == 0) {
  355. RAY_posX += FP_Mult(RAY_dirX, moveSpeed);
  356. }
  357. worldMapX = FP_FPtoInt(RAY_posX);
  358. worldMapY = FP_FPtoInt(RAY_posY + FP_Mult(RAY_dirY, moveSpeed + movePadding));
  359. if (worldMap[worldMapX][worldMapY] == 0) {
  360. RAY_posY += FP_Mult(RAY_dirY, moveSpeed);
  361. }
  362. } else if (BDOS_USBkeyHeld(BTN_DOWN)) {
  363. word worldMapX = FP_FPtoInt(RAY_posX - FP_Mult(RAY_dirX, moveSpeed + movePadding));
  364. word worldMapY = FP_FPtoInt(RAY_posY);
  365. if (worldMap[worldMapX][worldMapY] == 0) {
  366. RAY_posX -= FP_Mult(RAY_dirX, moveSpeed);
  367. }
  368. worldMapX = FP_FPtoInt(RAY_posX);
  369. worldMapY = FP_FPtoInt(RAY_posY - FP_Mult(RAY_dirY, moveSpeed + movePadding));
  370. if (worldMap[worldMapX][worldMapY] == 0) {
  371. RAY_posY -= FP_Mult(RAY_dirY, moveSpeed);
  372. }
  373. }
  374. if (HID_FifoAvailable()) {
  375. word c = HID_FifoRead();
  376. if (c == 27) // escape
  377. {
  378. GFX_clearPXframebuffer();
  379. quitGame = 1;
  380. }
  381. }
  382. }
  383. return 'q';
  384. }
  385. void interrupt() {
  386. // handle all interrupts
  387. word i = getIntID();
  388. switch (i) {
  389. case INTID_TIMER1:
  390. timer1Value = 1; // notify ending of timer1
  391. break;
  392. case INTID_TIMER2:
  393. break;
  394. case INTID_UART0:
  395. break;
  396. case INTID_GPU:
  397. break;
  398. case INTID_TIMER3:
  399. break;
  400. case INTID_PS2:
  401. break;
  402. case INTID_UART1:
  403. break;
  404. case INTID_UART2:
  405. break;
  406. }
  407. }