Advertisement
Guest User

Javascript raycasting test

a guest
Feb 18th, 2014
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.28 KB | None | 0 0
  1. ==== raycast.html ====
  2. <html>
  3. <head>
  4. <title>Stuff!</title>
  5. <script type="text/javascript" src="raycast.js"></script>
  6. <script type="text/javascript" src="maze.js"></script>
  7. <script type="text/javascript">
  8. <!--
  9.  
  10. function init() {
  11. raycaster_init();
  12. maze_init();
  13. genmaze();
  14. }
  15.  
  16. window.onload = init;
  17.  
  18. //-->
  19. </script>
  20. </head>
  21. <body>
  22. <div style="text-align:center;white-space:nowrap;font-family:monospace;font-size:8;background-color:black;color:white" id="divscreen"></div>
  23. <div style="text-align:center" id="divfps"></div>
  24. <div style="text-align:center">Up/Down move forward/backwards, Left/Right turn.</div>
  25. <div style="text-align:center;font-family:monospace" id="map"></div>
  26. <div id="debugscr"></div>
  27. </form>
  28. </body>
  29. </html>
  30.  
  31. ==== maze.js ====
  32. var maze_width = 10;
  33. var maze_height = 10;
  34.  
  35. var maze;
  36.  
  37. var start_char = "?";
  38. var end_char = "!";
  39. var norm_char = "#";
  40. var rand_char = "@$%&~*";
  41.  
  42. var maze_map;
  43.  
  44. var maze_map_width = maze_width*2 + 1;
  45. var maze_map_height = maze_height*2 + 1;
  46.  
  47. var mapdiv;
  48. var debugdiv;
  49.  
  50. function Cell(x, y) {
  51. this.north = true;
  52. this.west = true;
  53. this.touched = false;
  54. this.back = null;
  55. this.x = x;
  56. this.y = y;
  57. }
  58.  
  59. function maze_init() {
  60. maze_map = new Array(maze_map_height);
  61. for (var i = 0; i < maze_map_height; i++) {
  62. maze_map[i] = new Array(maze_map_width);
  63. }
  64.  
  65. mapdiv = document.getElementById("map");
  66. debugdiv = document.getElementById("debugscr");
  67. }
  68.  
  69. function genmaze() {
  70. maze = new Array(maze_height);
  71. for (var i = 0; i < maze_height; i++) {
  72. maze[i] = new Array(maze_width);
  73. for (var j = 0; j < maze_width; j++) {
  74. maze[i][j] = new Cell(j, i);
  75. }
  76. }
  77.  
  78. var currx = Math.floor(Math.random() * maze_width);
  79. var curry = Math.floor(Math.random() * maze_height);
  80.  
  81. var currcell = maze[curry][currx];
  82.  
  83. do { // This is the main depth-first-search loop, it creates our maze
  84.  
  85. // Touch the current cell
  86. currcell.touched = true;
  87.  
  88. // Get a random direction
  89. var valid_dirs = [];
  90. if (!(curry <= 0 || maze[curry-1][currx].touched || !currcell.north)) {
  91. valid_dirs.push(0);
  92. }
  93. if (!(curry >= maze_height-1 || maze[curry+1][currx].touched || !maze[curry+1][currx])) {
  94. valid_dirs.push(1);
  95. }
  96. if (!(currx <= 0 || maze[curry][currx-1].touched || !currcell.west)) {
  97. valid_dirs.push(2);
  98. }
  99. if (!(currx >= maze_width-1 || maze[curry][currx+1].touched || !maze[curry][currx+1].west)) {
  100. valid_dirs.push(3);
  101. }
  102.  
  103. // Are we stuck?
  104. if (valid_dirs.length == 0) {
  105. // Roll back
  106. currcell = currcell.back;
  107. if (currcell != null) {
  108. currx = currcell.x;
  109. curry = currcell.y;
  110. }
  111. continue;
  112. }
  113.  
  114. // We're not stuck, move on
  115. var dir = valid_dirs[Math.floor(Math.random()*valid_dirs.length)];
  116.  
  117. switch(dir) {
  118. case 0: // head north
  119. // knock out the north wall
  120. currcell.north = false;
  121.  
  122. // move to the north cell
  123. curry--;
  124. maze[curry][currx].back = currcell;
  125. currcell = maze[curry][currx];
  126. break;
  127.  
  128. case 1: // head south
  129. // knock out the south wall
  130. maze[curry+1][currx].north = false;
  131.  
  132. // move to the south cell
  133. curry++;
  134. maze[curry][currx].back = currcell;
  135. currcell = maze[curry][currx];
  136. break;
  137.  
  138. case 2: // head west
  139. // knock out the west wall
  140. currcell.west = false;
  141.  
  142. // move to the west cell
  143. currx--;
  144. maze[curry][currx].back = currcell;
  145. currcell = maze[curry][currx];
  146. break;
  147.  
  148. case 3: // head east
  149. // knock out the east wall
  150. maze[curry][currx+1].west = false;
  151.  
  152. // move to the north cell
  153. currx++;
  154. maze[curry][currx].back = currcell;
  155. currcell = maze[curry][currx];
  156. break;
  157. } // switch (dir)
  158. } while (currcell != null); // This is when we know we've reached every cell.
  159.  
  160. // Now we have to convert the maze data into a map for the raycaster
  161. // Build the base map - map boundaries and known internal corners only
  162. for (var i = 0; i < maze_map_height; i++) {
  163. for (var j = 0; j < maze_map_width; j++) {
  164. // Boundaries
  165. if (i == 0 || i == maze_map_height-1 || j == 0 || j == maze_map_width-1) {
  166. maze_map[i][j] = norm_char;
  167. }
  168. // Internal corners
  169. else if (i % 2 == 0 && j % 2 == 0) {
  170. maze_map[i][j] = norm_char;
  171. }
  172. else {
  173. maze_map[i][j] = "";
  174. }
  175. }
  176. }
  177.  
  178. // Put in walls
  179. for (var i = 0; i < maze_height; i++) {
  180. for (var j = 0; j < maze_width; j++) {
  181. // North wall
  182. if (i > 0 && maze[i][j].north) {
  183. // Make it fake?
  184. if (Math.random() < 0.9) {
  185. maze_map[2*i][2*j+1] = norm_char;
  186. }
  187. else {
  188. maze_map[2*i][2*j+1] = "F";
  189. }
  190. }
  191. // West wall
  192. if (j > 0 && maze[i][j].west) {
  193. // Make it fake?
  194. if (Math.random() < 0.9) {
  195. maze_map[2*i+1][2*j] = norm_char;
  196. }
  197. else {
  198. maze_map[2*i+1][2*j] = "F";
  199. }
  200. }
  201. }
  202. }
  203.  
  204. // Fill in start/end spaces
  205. var startx = 1;
  206. var starty = 1;
  207. var endx = maze_map_width-2;
  208. var endy = maze_map_height-2;
  209.  
  210. for (var i = -1; i <= 1; i++) {
  211. for (var j = -1; j <= 1; j++) {
  212. if(maze_map[i+starty][j+startx]) {
  213. maze_map[i+starty][j+startx] = start_char;
  214. }
  215. if(maze_map[i+endy][j+endx]) {
  216. maze_map[i+endy][j+endx] = end_char;
  217. }
  218. }
  219. }
  220.  
  221. // Draw out the map
  222. for (var i = 0; i < maze_map_height; i++) {
  223. for (var j = 0; j < maze_map_width; j++) {
  224. if (maze_map[i][j]) {
  225. mapdiv.innerHTML = mapdiv.innerHTML + maze_map[i][j];
  226. }
  227. else {
  228. mapdiv.innerHTML = mapdiv.innerHTML + "&nbsp;";
  229. }
  230. }
  231. mapdiv.innerHTML = mapdiv.innerHTML + "<br/>";
  232. }
  233.  
  234. // Set the map
  235. map = maze_map;
  236. playerx = 1.5*tile_size;
  237. playery = 1.5*tile_size;
  238. }
  239.  
  240. ==== raycast.js ====
  241. var screen;
  242. var fpsdiv;
  243. var lines;
  244.  
  245. var screen_width = 320;
  246. var screen_height = 100;
  247.  
  248. var scanline_0;
  249. var coord_line;
  250. var map_coord_line;
  251.  
  252. var floorc = ".";
  253. var ceilc = ".";
  254.  
  255. /* This was for debugging the raycaster, it's no longer necessary
  256. var map = [
  257. ["#","#","#","#","#","#","#","#","#","#","#","#"], // ############
  258. ["#","" ,"" ,"@","" ,"" ,"" ,"" ,"" ,"" ,"" ,"#"], // # @ #
  259. ["#","@","" ,"" ,"" ,"$","" ,"!","!","!","" ,"#"], // #@ $ !!! #
  260. ["#","" ,"" ,"@","" ,"$","" ,"" ,"" ,"!","" ,"#"], // # @ $ ! #
  261. ["#","" ,"@","" ,"" ,"$","" ,"!","!","!","" ,"#"], // # @ $ !!! #
  262. ["#","#","#","#","#","#","#","#","#","#","#","#"] // ############
  263. ];
  264. */
  265.  
  266. var map;
  267.  
  268. var max_turn_rate = Math.PI/50;
  269. var turn_rate_accel = Math.PI/160;
  270. var max_move_rate = 9;
  271. var move_rate_accel = 1;
  272. var player_rad = 10; // need to keep player from walking right up to a wall, it causes issues
  273.  
  274. var turn_rate = 0;
  275. var move_rate = 0;
  276. var strafe_rate = 0;
  277.  
  278. var view_dist = 144;
  279. var view_width = 230;
  280. var view_height = 144;
  281. var view_angle = 2*Math.atan(0.5*view_width/view_dist);
  282.  
  283. var tile_size = 100;
  284. var wall_height = 80;
  285.  
  286. var playerx = 2.5*tile_size;
  287. var playery = 2.5*tile_size;
  288. var playerd = 0;
  289.  
  290. var lastdrawx = -1;
  291. var lastdrawy = -1;
  292. var lastdrawc = "";
  293. var lastdir = -1;
  294. var HORIZ = 0;
  295. var VERT = 1;
  296.  
  297. var keys = [false, false, false, false, false, false];
  298. var UP = 0;
  299. var DOWN = 1;
  300. var LEFT = 2;
  301. var RIGHT = 3;
  302. var KEY_A = 4;
  303. var KEY_S = 5;
  304.  
  305. var must_redraw = true;
  306.  
  307. var stop = false;
  308.  
  309. var frames = 0;
  310. var msecs = 0;
  311.  
  312. function raycaster_init() {
  313. // Get screen div
  314. screen = document.getElementById("divscreen");
  315.  
  316. // Get fps div
  317. fpsdiv = document.getElementById("divfps");
  318.  
  319. // Add a variety of text nodes
  320. var nodeidx = screen.childNodes.length;
  321. scanline_0 = nodeidx;
  322.  
  323. for (var i = 0; i < screen_height + scanline_0; i++) {
  324. screen.appendChild(document.createTextNode("")); nodeidx++;
  325. screen.appendChild(document.createElement("br")); nodeidx++;
  326. }
  327.  
  328. // Data lines
  329. coord_line = nodeidx;
  330. screen.appendChild(document.createTextNode("")); nodeidx++;
  331. screen.appendChild(document.createElement("br")); nodeidx++;
  332.  
  333. map_coord_line = nodeidx;
  334. screen.appendChild(document.createTextNode("")); nodeidx++;
  335. screen.appendChild(document.createElement("br")); nodeidx++;
  336.  
  337.  
  338.  
  339. // Set up lines array
  340. lines = new Array(screen_height);
  341.  
  342. // Set up key event handling
  343. window.onkeydown = function(e) {
  344. var key = e.keyCode;
  345. if (key == 38) { keys[UP] = true; return false; }
  346. else if (key == 40) { keys[DOWN] = true; return false; }
  347. else if (key == 37) { keys[LEFT] = true; return false; }
  348. else if (key == 39) { keys[RIGHT] = true; return false; }
  349. else if (key == 65) { keys[KEY_A] = true; return false; }
  350. else if (key == 83) { keys[KEY_S] = true; return false; }
  351. else if (key == 81) { stop = true; return false; } // q
  352. }
  353. window.onkeyup = function(e) {
  354. var key = e.keyCode;
  355. if (key == 38) { keys[UP] = false; return false; }
  356. else if (key == 40) { keys[DOWN] = false; return false; }
  357. else if (key == 37) { keys[LEFT] = false; return false; }
  358. else if (key == 39) { keys[RIGHT] = false; return false; }
  359. else if (key == 65) { keys[KEY_A] = false; return false; }
  360. else if (key == 83) { keys[KEY_S] = false; return false; }
  361. }
  362.  
  363. must_redraw = true;
  364.  
  365. // Set up the update function
  366. window.setTimeout(update, 10);
  367. // Set up the counter function
  368. window.setInterval(timing, 200);
  369. }
  370.  
  371. function timing() {
  372. msecs += 200;
  373. }
  374.  
  375. function update() {
  376. frames += 1;
  377.  
  378. var redraw = true; // Testing efficiency
  379.  
  380. // Handle player movement
  381. with(Math) {
  382. // First, adjust turn rate
  383. if (keys[LEFT]) {
  384. turn_rate += turn_rate_accel;
  385. if (turn_rate > max_turn_rate) {
  386. turn_rate = max_turn_rate;
  387. }
  388. }
  389. else if (keys[RIGHT]) {
  390. turn_rate -= turn_rate_accel;
  391. if (turn_rate < -max_turn_rate) {
  392. turn_rate = -max_turn_rate;
  393. }
  394. }
  395. else {
  396. if (turn_rate < 0) {
  397. turn_rate += 2*turn_rate_accel;
  398. if (turn_rate > 0) {
  399. turn_rate = 0;
  400. }
  401. }
  402. else {
  403. turn_rate -= 2*turn_rate_accel;
  404. if (turn_rate < 0) {
  405. turn_rate = 0;
  406. }
  407. }
  408. }
  409.  
  410. // Turn player
  411. if (turn_rate != 0) {
  412. playerd += turn_rate;
  413. if (playerd >= 2*PI) {
  414. playerd -= 2*PI;
  415. }
  416. else if (playerd < 0) {
  417. playerd += 2*PI;
  418. }
  419. redraw = true;
  420. }
  421.  
  422. // Now, move the player forward/backward
  423. if (keys[UP]) {
  424. move_rate += move_rate_accel;
  425. if (move_rate > max_move_rate) {
  426. move_rate = max_move_rate;
  427. }
  428. }
  429. else if (keys[DOWN]) {
  430. move_rate -= move_rate_accel;
  431. if (move_rate < -max_move_rate) {
  432. move_rate = -max_move_rate;
  433. }
  434. }
  435. else {
  436. if (move_rate < 0) {
  437. move_rate += 2*move_rate_accel;
  438. if (move_rate > 0) {
  439. move_rate = 0;
  440. }
  441. }
  442. else {
  443. move_rate -= 2*move_rate_accel;
  444. if (move_rate < 0) {
  445. move_rate = 0;
  446. }
  447. }
  448. }
  449.  
  450. // Move player
  451. if (move_rate != 0 ) {
  452. // Check for collision in the cheapest way possible
  453. var tempx = playerx + cos(playerd)*move_rate;
  454. var tempy = playery + sin(playerd)*move_rate;
  455. var collx = tempx + cos(playerd) * player_rad * (move_rate < 0 ? -1 : 1);
  456. var colly = tempy + sin(playerd) * player_rad * (move_rate < 0 ? -1 : 1);
  457. // Breaking a fake wall?
  458. if (map[floor(colly/tile_size)][floor(collx/tile_size)] == "F") {
  459. map[floor(colly/tile_size)][floor(collx/tile_size)] = "";
  460. }
  461. // Will we end up in blank space?
  462. if (!map[floor(colly/tile_size)][floor(collx/tile_size)]) {
  463. playerx = tempx;
  464. playery = tempy;
  465. redraw = true;
  466. }
  467. }
  468.  
  469. // Strafe left/right
  470. if (keys[KEY_A]) {
  471. strafe_rate += move_rate_accel;
  472. if (strafe_rate > max_move_rate) {
  473. strafe_rate = max_move_rate;
  474. }
  475. }
  476. else if (keys[KEY_S]) {
  477. strafe_rate -= move_rate_accel;
  478. if (strafe_rate < -max_move_rate) {
  479. strafe_rate = -max_move_rate;
  480. }
  481. }
  482. else {
  483. if (strafe_rate < 0) {
  484. strafe_rate += 2*move_rate_accel;
  485. if (strafe_rate > 0) {
  486. strafe_rate = 0;
  487. }
  488. }
  489. else {
  490. strafe_rate -= 2*move_rate_accel;
  491. if (strafe_rate < 0) {
  492. strafe_rate = 0;
  493. }
  494. }
  495. }
  496.  
  497. // Move player for strafing
  498. if (strafe_rate != 0) {
  499. // Check for collision in the cheapest way possible
  500. var tempx = playerx + cos(playerd+PI/2)*strafe_rate;
  501. var tempy = playery + sin(playerd+PI/2)*strafe_rate;
  502. var collx = tempx + cos(playerd+PI/2) * player_rad * (strafe_rate < 0 ? -1 : 1);
  503. var colly = tempy + sin(playerd+PI/2) * player_rad * (strafe_rate < 0 ? -1 : 1);
  504. // Breaking a fake wall?
  505. if (map[floor(colly/tile_size)][floor(collx/tile_size)] == "F") {
  506. map[floor(colly/tile_size)][floor(collx/tile_size)] = "";
  507. }
  508. // Will we end up in blank space?
  509. if (!map[floor(colly/tile_size)][floor(collx/tile_size)]) {
  510. playerx = tempx;
  511. playery = tempy;
  512. redraw = true;
  513. }
  514. }
  515. }
  516.  
  517. // Must we redraw the screen?
  518. if (redraw || must_redraw) {
  519. // Clear screen
  520. for (var i = 0; i < screen_height; i++) {
  521. lines[i] = "";
  522. }
  523.  
  524. // Reset outline information
  525. lastdrawx = -1;
  526. lastdrawy = -1;
  527. lastdir = -1;
  528.  
  529. // Cast rays
  530. for (var i = 0; i < screen_width; i++) {
  531. raycast(i)
  532. }
  533.  
  534. // Draw screen
  535. write_screen();
  536.  
  537. // Turn off must_redraw, unless we were going to redraw anyway
  538. if (!redraw) {
  539. must_redraw = false;
  540. }
  541. }
  542.  
  543. if (!stop) {
  544. window.setTimeout(update, 10);
  545. }
  546. }
  547.  
  548. function raycast(n) {
  549.  
  550. var dir = playerd - (n/screen_width - 0.5)*view_angle;
  551. var xbase = Math.cos(dir);
  552. var ybase = Math.sin(dir);
  553. var north = (ybase < 0 ? true : false);
  554. var east = (xbase > 0 ? true : false);
  555. var zerox = xbase == 0;
  556. var zeroy = ybase == 0;
  557.  
  558. var currx = playerx;
  559. var curry = playery;
  560. var currmapx = Math.floor(playerx/tile_size);
  561. var currmapy = Math.floor(playery/tile_size);
  562.  
  563. var nexthedgex, nexthedgey, nextvedgex, nextvedgey;
  564.  
  565. var thisdir = -1;
  566. while(!map[currmapy][currmapx]) {
  567. // Calculate the next horizontal edge
  568. if (north) {
  569. nexthedgey = Math.ceil(curry/tile_size - 1) * tile_size;
  570. }
  571. else {
  572. nexthedgey = Math.floor(curry/tile_size + 1) * tile_size;
  573. }
  574. nexthedgex = currx + (xbase/ybase)*(nexthedgey-curry);
  575.  
  576. // Calculate the next vertical edge
  577. if (east) {
  578. nextvedgex = Math.floor(currx/tile_size + 1) * tile_size;
  579. }
  580. else {
  581. nextvedgex = Math.ceil(currx/tile_size - 1) * tile_size;
  582. }
  583. nextvedgey = curry + (ybase/xbase)*(nextvedgex-currx);
  584.  
  585. // Determine which is nearer
  586. if ((nexthedgex - currx)*(nexthedgex - currx) + (nexthedgey - curry)*(nexthedgey - curry) >
  587. (nextvedgex - currx)*(nextvedgex - currx) + (nextvedgey - curry)*(nextvedgey - curry)) {
  588. // Vertical edge is nearer
  589. currx = nextvedgex;
  590. curry = nextvedgey;
  591.  
  592. // Get the next map cell
  593. if (east) {
  594. // Cell is to the right
  595. currmapx++;
  596. }
  597. else {
  598. // Cell is to the left
  599. currmapx--;
  600. }
  601. if (map[currmapy][currmapx]) {
  602. thisdir = VERT;
  603. }
  604. }
  605. else {
  606. // Horizontal edge is nearer
  607. currx = nexthedgex;
  608. curry = nexthedgey;
  609.  
  610. // Get the next map cell
  611. if (north) {
  612. // Cell is northwards
  613. currmapy--;
  614. }
  615. else {
  616. // Cell is southwards
  617. currmapy++;
  618. }
  619. if (map[currmapy][currmapx]) {
  620. thisdir = HORIZ;
  621. }
  622. }
  623. }
  624.  
  625. // We've found a wall! Now to get the distance
  626. var baddist = Math.sqrt((currx-playerx)*(currx-playerx)+(curry-playery)*(curry-playery));
  627.  
  628. // Fisheye correction
  629. var dist = baddist * Math.cos(dir-playerd);
  630.  
  631. // Calculate display height of wall
  632. var wall_view_height = wall_height * view_dist / dist;
  633. var dispheight = Math.ceil(wall_view_height / view_height * screen_height);
  634.  
  635. // Detrmine number of ceiling, wall, and floor lines to use
  636. var ceilpx, wallpx, floorpx;
  637.  
  638. // Wall taller than screen?
  639. if (dispheight > screen_height) {
  640. // Display only wall
  641. ceilpx = 0;
  642. wallpx = screen_height;
  643. floorpx = 0;
  644. }
  645. else {
  646. // Display wall at dispheight and floor/ceil at equal heights
  647. ceilpx = Math.ceil((screen_height - dispheight)/2);
  648. wallpx = dispheight;
  649. floorpx = Math.floor((screen_height - dispheight)/2);
  650. }
  651.  
  652. // Where do I start drawing the wall and the floor?
  653. var wallloc = ceilpx;
  654. var floorloc = ceilpx + wallpx;
  655.  
  656. // Should we draw a black outline?
  657. var outline = false;
  658. if (i == 0 || i == screen_width-1) {
  659. // Always draw the leftmost and rightmost columns
  660. }
  661. else if ((lastdrawx != currmapx && lastdrawy != currmapy) || lastdir != thisdir ||
  662. lastdrawc != map[currmapy][currmapx]) {
  663. // Draw an outline
  664. outline = true;
  665. }
  666.  
  667. lastdrawx = currmapx;
  668. lastdrawy = currmapy;
  669. lastdrawc = map[currmapy][currmapx];
  670. lastdir = thisdir;
  671.  
  672. // Now to draw to lines array
  673. for (var i = 0; i < screen_height; i++) {
  674. if (i >= floorloc) {
  675. lines[i] = lines[i] + floorc;
  676. }
  677. else if (i >= wallloc) {
  678. if (outline) {
  679. lines[i] = lines[i] + "\u00a0"; // non-breaking space
  680. }
  681. else if (map[currmapy][currmapx] == "F") {
  682. lines[i] = lines[i] + "#"; // fake wall
  683. }
  684. else {
  685. lines[i] = lines[i] + map[currmapy][currmapx];
  686. }
  687. }
  688. else {
  689. lines[i] = lines[i] + ceilc;
  690. }
  691. }
  692. } // function raycast
  693.  
  694. function write_screen() {
  695. for (var i = 0; i < screen_height; i++) {
  696. // i*2 to skip over br elements
  697. screen.childNodes[scanline_0 + i*2].data = lines[i];
  698. }
  699. screen.childNodes[coord_line].data = playerx + ", " + playery + " (dir " + playerd + ")";
  700. screen.childNodes[map_coord_line].data = "Map coord: " + Math.floor(playerx/tile_size) + ", " + Math.floor(playery/tile_size);
  701.  
  702. fpsdiv.innerHTML = (msecs == 0 ? "??" : Math.round(1000*frames/msecs));
  703. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement