From c8d8c22478bfdcf7d39dee8d1a6a2f2f5ea19554 Mon Sep 17 00:00:00 2001 From: Martin Haye Date: Fri, 30 May 2014 08:33:11 -0700 Subject: [PATCH] Use real map data in Javascript raycaster, so I can more closely simulate the Apple II and thus debug problems more easily. --- .../src/org/demo/PackPartitions.groovy | 82 ++++++++++- .../virtual/src/raycast/javascript/intcast.js | 133 +++++++++++------- 2 files changed, 157 insertions(+), 58 deletions(-) diff --git a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy index ec0b5994..b19f5e89 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy @@ -40,6 +40,8 @@ class PackPartitions def debugCompression = false + def javascriptOut = null + def parseMap(map, tiles) { // Parse each row of the map @@ -348,11 +350,59 @@ class PackPartitions } } } + + /** + * Dump map data to Javascript code, to help in debugging the raycaster. This way, + * the Javascript version can run the same map, and we can compare its results to + * the 6502 results. + */ + def dumpJsMap(rows, texMap) + { + def width = rows[0].size() + + // Write the map data. First comes the sentinel row. + javascriptOut.println("var map = [") + javascriptOut.print(" [") + (0..width).each { javascriptOut.print("-1,") } + javascriptOut.println("],") + + // Now the real map data + rows.each { row -> + javascriptOut.print(" [") + row.each { tile -> + def b = texMap[tile?.@id] + if ((b & 0x80) == 0) + javascriptOut.format("%2d,", b) + else + javascriptOut.print(" 0,") + } + javascriptOut.println("-1,],") + } + + // Finish the map data with another sentinel row + javascriptOut.print(" [") + (0..width).each { javascriptOut.print("-1,") } + javascriptOut.println("]") + javascriptOut.println("];\n") + + // Then write out the sprites + javascriptOut.println("var allSprites = [") + rows.eachWithIndex { row, y -> + row.eachWithIndex { tile, x -> + def b = texMap[tile?.@id] + if ((b & 0x80) != 0) { + // y+1 below to account for initial sentinel row + javascriptOut.format(" {type:%2d, x:%2d.5, y:%2d.5},\n", b & 0x7f, x, y+1) + } + } + } + javascriptOut.println("];\n") + } def write3DMap(buf, mapName, rows) // [ref BigBlue1_50] { - def width = rows[0].size() - def height = rows.size() + def width = rows[0].size() + 1 // Sentinel $FF at end of each row + def height = rows.size() + 2 // Sentinel rows of $FF's at start and end // Determine the set of all referenced textures, and assign numbers to them. def texMap = [:] @@ -389,12 +439,22 @@ class PackPartitions texList.each { buf.put((byte)it) } buf.put((byte)0) + // Sentinel row of $FF at start of map + (0..width).each { buf.put((byte)0xFF) } + // After the header comes the raw data rows.each { row -> row.each { tile -> buf.put((byte)texMap[tile?.@id]) } + buf.put((byte)0xFF) // sentinel at end of row } + + // Sentinel row of $FF at end of map + (0..width).each { buf.put((byte)0xFF) } + + if (javascriptOut) + dumpJsMap(rows, texMap) } // The renderer wants bits of the two pixels interleaved in a special way. @@ -697,7 +757,7 @@ class PackPartitions } } - def pack(xmlPath, binPath) + def pack(xmlPath, binPath, javascriptPath) { // Read in code chunks. For now these are hard coded, but I guess they ought to // be configured in a config file somewhere...? @@ -731,6 +791,10 @@ class PackPartitions packTexture(image) } + // If doing Javascript debugging, open that output file too. + if (javascriptPath) + javascriptOut = new File(javascriptPath).newPrintWriter() + // Pack each map This uses the image and tile maps filled earlier. println "Packing maps." dataIn.map.each { map -> @@ -746,6 +810,11 @@ class PackPartitions println "Writing output file." new File(binPath).withOutputStream { stream -> writePartition(stream) } + // Finish up Javacript if necessary + if (javascriptPath) + javascriptOut.close() + + // Lastly, print stats println "Compression saved $compressionSavings bytes." if (compressionSavings > 0) { def endSize = new File(binPath).length() @@ -773,13 +842,14 @@ class PackPartitions } // Check the arguments - if (args.size() != 2) { - println "Usage: convert yourOutlawFile.xml DISK.PART.0.bin" + if (args.size() != 2 && args.size() != 3) { + println "Usage: convert yourOutlawFile.xml game.part.0.bin [intcastMap.js]" + println " (where intcastMap.js is to aid in debugging the Javascript raycaster)" System.exit(1); } // Go for it. - new PackPartitions().pack(args[0], args[1]) + new PackPartitions().pack(args[0], args[1], args.size() > 2 ? args[2] : null) } } diff --git a/Platform/Apple/virtual/src/raycast/javascript/intcast.js b/Platform/Apple/virtual/src/raycast/javascript/intcast.js index c8c1695c..90e975e9 100644 --- a/Platform/Apple/virtual/src/raycast/javascript/intcast.js +++ b/Platform/Apple/virtual/src/raycast/javascript/intcast.js @@ -4,55 +4,77 @@ var $ = function(id) { return document.getElementById(id); }; var dc = function(tag) { return document.createElement(tag); }; var map = [ - [1,4,3,4,2,3,2,4,3,2,4,3,4], - [1,0,0,1,0,0,0,3,0,0,2,0,3], - [1,1,0,1,1,0,0,1,0,0,3,0,2], - [1,0,0,1,2,3,0,4,0,0,4,0,3], - [1,0,0,0,0,4,0,0,0,0,0,0,4], - [2,0,0,0,0,2,0,0,0,0,0,0,4], - [0,2,2,2,0,0,0,3,0,0,3,0,1], - [3,0,0,2,0,0,0,3,0,0,2,0,3], - [1,0,0,2,0,3,0,2,0,0,4,0,3], - [1,0,0,0,0,2,0,0,0,0,0,0,1], - [3,0,0,0,0,1,0,0,0,0,0,0,3], - [1,0,0,4,0,4,0,3,1,2,4,0,2], - [4,0,0,0,0,0,0,0,0,0,0,0,3], - [1,2,3,3,3,2,2,1,2,4,2,2,2] + [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,], + [ 0, 1, 2, 0, 1, 0, 0, 3, 0, 0, 1, 4, 5, 1, 0, 0, 0,-1,], + [ 4, 0, 0, 7, 0, 2, 1, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0,-1,], + [ 0, 5, 0, 0, 0, 0, 0, 0, 7, 1, 8, 9, 0, 2, 1, 0, 0,-1,], + [ 1, 0, 0, 2, 0,10,11, 0, 2, 0, 0, 8, 0, 7, 1, 0, 0,-1,], + [ 0, 0, 0, 1, 9, 0,12, 0, 0, 0, 4, 5, 0, 0, 0, 3, 0,-1,], + [ 3, 0, 0, 0, 0,12,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,], + [ 0, 4, 5, 2, 0, 0, 0, 0,14,15, 0, 7, 2, 0, 0, 0, 0,-1,], + [ 2, 0, 0, 7, 0, 4, 5, 0, 2,14, 0, 9, 7, 0, 0, 2, 0,-1,], + [ 0, 1, 0, 1, 0,11,10, 0, 8, 0, 0, 4, 0,10,11, 0, 0,-1,], + [ 0, 8, 0, 0, 0,10,11, 0, 9, 0, 0, 5, 0, 0, 0, 0, 0,-1,], + [ 0, 8, 9, 0, 1, 1, 0, 3, 0, 0, 0, 0, 0,10, 0, 0, 0,-1,], + [ 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0,-1,], + [ 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0,-1,], + [ 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,-1,], + [ 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,-1,], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,], + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1,], + [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,] ]; var itemTypes = [ { img : "sprites/tablechairs.png", block : true }, // 0 { img : "sprites/armor.png", block : true }, // 1 { img : "sprites/plantgreen.png", block : true }, // 2 - { img : "sprites/lamp.png", block : false } // 3 + { img : "sprites/lamp.png", block : false }, // 3 + { img : "sprites/lamp.png", block : false }, // 4 + { img : "sprites/lamp.png", block : false }, // 5 + { img : "sprites/lamp.png", block : false } // 6 ]; var allSprites = [ - - // lamp where tree would be - {type:3, x:1.5, y:4.5}, - - // lamps in center area - {type:3, x:9.5, y:7.5}, - {type:3, x:15.5, y:7.5}, - - // lamps in bottom corridor - {type:3, x:5.5, y:12.5}, - {type:3, x:11.5, y:12.5}, - {type:3, x:11.5, y:12.5}, - - // tables in long bottom room - {type:0, x:10.5, y:10.5}, - {type:1, x:11.5, y:10.5}, - // lamps in long bottom room - {type:3, x:8.5, y:10.5}, - {type:3, x:9.5, y:10.5} + {type: 6, x: 1.5, y: 5.5}, + {type: 6, x:14.5, y: 1.5}, + {type: 6, x: 4.5, y: 4.5}, + {type: 6, x: 8.5, y: 5.5}, + {type: 6, x:14.5, y: 6.5}, + {type: 6, x:15.5, y: 7.5}, + {type: 6, x: 9.5, y: 9.5}, + {type: 6, x:12.5, y:10.5}, + {type: 6, x:14.5, y:10.5}, + {type: 6, x: 8.5, y:11.5}, + {type: 6, x:10.5, y:11.5}, + {type: 6, x:12.5, y:11.5}, + {type: 6, x: 6.5, y:12.5}, + {type: 6, x: 8.5, y:12.5}, + {type: 6, x: 9.5, y:12.5}, + {type: 6, x:10.5, y:12.5}, + {type: 6, x:11.5, y:12.5}, + {type: 6, x: 7.5, y:13.5}, + {type: 6, x: 9.5, y:13.5}, + {type: 6, x: 6.5, y:14.5}, + {type: 6, x:10.5, y:14.5}, + {type: 6, x: 8.5, y:15.5}, + {type: 6, x:12.5, y:15.5}, + {type: 6, x: 1.5, y:16.5}, + {type: 6, x: 3.5, y:16.5}, + {type: 6, x: 5.5, y:16.5}, + {type: 6, x: 6.5, y:16.5}, + {type: 6, x: 7.5, y:16.5}, + {type: 6, x: 9.5, y:16.5}, + {type: 6, x:10.5, y:16.5}, + {type: 6, x:11.5, y:16.5}, + {type: 6, x:13.5, y:16.5}, + {type: 6, x:14.5, y:16.5}, ]; // Player attributes [ref BigBlue2_10] var player = { x : 1.5, // current x, y position - y : 2.5, + y : 3.5, dir : 0, // the direction that the player is turning, either -1 for left or 1 for right. angleNum : 4, // the current angle of rotation speed : 0, // is the playing moving forward (speed = 1) or backwards (speed = -1). @@ -100,9 +122,6 @@ var wallTextures = [ var userAgent = navigator.userAgent.toLowerCase(); var isGecko = userAgent.indexOf("gecko") != -1 && userAgent.indexOf("safari") == -1; -// enable this to use a single image file containing all wall textures. This performs better in Firefox. Opera likes smaller images. -var useSingleTexture = isGecko; - var screenStrips = []; var overlay; @@ -413,6 +432,15 @@ function intRenderSprites() // translate position to viewer space var dx = sprite.x - player.x; var dy = sprite.y - player.y; + + // Prevent things from overflowing + if (dx > 8 || dy > 8) { + if (sprite.index == debugSprite) + console.log(" too far away (dx=" + dx + ", dy=" + dy + ")"); + sprite.visible = false; + sprite.img.style.display = "none"; + continue; + } // Apply rotation to the position var bSgnDx = (dx < 0) ? -1 : 1; @@ -470,7 +498,7 @@ function intRenderSprites() var wSpriteTop = 32 - (wSize >> 1); var wSpriteLeft = wX + wSpriteTop; if (sprite.index == debugSprite) - console.log(" wX=$" + wordToHex(wX & 0xFFFF) + ", wSpriteTop=$" + wordToHex(wSpriteTop) + ", wSpriteLeft=$" + wordToHex(wSpriteLeft & 0xFFFF)); + console.log(" wX=$" + wordToHex(wX) + ", wSpriteTop=$" + wordToHex(wSpriteTop) + ", wSpriteLeft=$" + wordToHex(wSpriteLeft)); var bStartTx = 0; if (wSpriteLeft < 0) { if (wSpriteLeft < -wSize) { @@ -579,10 +607,6 @@ function initScreen() { strip.style.left = 0 + "px"; strip.style.height = "0px"; - if (useSingleTexture) { - strip.src = "walls/walls.png"; - } - strip.oldStyles = { left : 0, top : 0, @@ -728,14 +752,16 @@ function assert(flg, msg) { } function byteToHex(d) { - assert(d >= 0 && d <= 255, "byte out of range"); - var hex = Number(d).toString(16).toUpperCase(); + assert(d >= -127 && d <= 255, "byte out of range"); + d = Number(d) & 0xFF; + var hex = d.toString(16).toUpperCase(); return "00".substr(0, 2 - hex.length) + hex; } function wordToHex(d) { - assert(d >= 0 && d <= 65535, "word out of range"); - var hex = Number(d).toString(16).toUpperCase(); + assert(d >= -32767 && d <= 65535, "word out of range"); + d = Number(d) & 0xFFFF; + var hex = d.toString(16).toUpperCase(); return "0000".substr(0, 4 - hex.length) + hex; } @@ -976,6 +1002,10 @@ function intCast(x) // Perform DDA - digital differential analysis while (true) { + // Return immediately if we hit the edge of the map. + if (bMapX < 0 || bMapY < 0 || map[bMapY][bMapX] < 0) + return { wallType: -1, textureX: 0, height: 0, logHeight: 1, xWallHit: 0, yWallHit: 0 } + // Jump to next map square in x-direction, OR in y-direction if (uless_bb(bSideDistX, bSideDistY)) { bMapX += bStepX; @@ -1060,12 +1090,11 @@ function drawStrip(stripIdx, lineData) var imgTop = 0; var styleHeight; - if (useSingleTexture) { - // then adjust the top placement according to which wall texture we need - imgTop = Math.floor(height * (lineData.wallType-1)); - var styleHeight = Math.floor(height * numTextures); + if (lineData.wallType < 0) { + var styleSrc = wallTextures[0]; + var styleHeight = 0; } else { - var styleSrc = wallTextures[lineData.wallType-1]; + var styleSrc = wallTextures[(lineData.wallType-1) % wallTextures.length]; if (strip.oldStyles.src != styleSrc) { strip.src = styleSrc; strip.oldStyles.src = styleSrc