mirror of
https://github.com/badvision/lawless-legends.git
synced 2025-01-11 21:29:46 +00:00
Coding real raycasting on 6502.
This commit is contained in:
parent
df312acf32
commit
4d44ad8723
7381
Platform/Apple/virtual/src/raycast/expand.s
Normal file
7381
Platform/Apple/virtual/src/raycast/expand.s
Normal file
File diff suppressed because it is too large
Load Diff
300
Platform/Apple/virtual/src/raycast/genExpand.py
Executable file
300
Platform/Apple/virtual/src/raycast/genExpand.py
Executable file
@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This program generates optimized texture expansion code.
|
||||
|
||||
import copy, os, re, sys
|
||||
|
||||
# Main control on size vs. speed
|
||||
rowInterval = 5
|
||||
|
||||
# Height of textures, in pixels
|
||||
textureHeight = 64
|
||||
|
||||
# Screen height, in pixels
|
||||
screenHeight = 128
|
||||
|
||||
# Other variables
|
||||
allSegs = {}
|
||||
allHeights = []
|
||||
finalSuffixMap = {}
|
||||
dstHeights = set()
|
||||
|
||||
outFile = open("expand.s", "w")
|
||||
outFile.write(" .pc02\n")
|
||||
outFile.write("pshape = 6\n")
|
||||
outFile.write("rowblit = $6000\n")
|
||||
outFile.write("\n")
|
||||
|
||||
def calcKey(cmds):
|
||||
return ".".join([str(c) for c in cmds])
|
||||
|
||||
# Make labels that are compact, unique, and representative.
|
||||
# The letters below happen to make lots of semi-words that
|
||||
# sound like "Totoro", which pleases me :)
|
||||
#
|
||||
def calcLabel(cmds):
|
||||
label = "e_"
|
||||
didNum = False
|
||||
for c in cmds:
|
||||
if c == "<":
|
||||
label += "r"
|
||||
elif c == "L":
|
||||
label += "t"
|
||||
elif didNum:
|
||||
label += "o"
|
||||
else:
|
||||
label += str(c)
|
||||
didNum = True
|
||||
return label
|
||||
|
||||
# Determine how much space a given set of commands will take in code
|
||||
def calcSpace(cmds):
|
||||
space = 0 # for RTS
|
||||
n = 0
|
||||
for c in cmds:
|
||||
if c == "L":
|
||||
space += 3 # LDA (ptr),y / INY
|
||||
elif c == "<":
|
||||
space += 1 # LSR
|
||||
else:
|
||||
n += c
|
||||
space += 3 # STA row,x
|
||||
return space
|
||||
|
||||
allLabels = set()
|
||||
generatedLabels = set()
|
||||
|
||||
class Segment:
|
||||
def __init__(s, cmds):
|
||||
s.cmds = cmds
|
||||
s.key = calcKey(cmds)
|
||||
s.label = calcLabel(cmds)
|
||||
s.space = calcSpace(s.cmds) + 1
|
||||
s.refs = 1
|
||||
s.truncLabel = None
|
||||
s.truncPos = None
|
||||
s.generated = False
|
||||
|
||||
def finalSuffix(s):
|
||||
return ".".join(calcKey(s.cmds[-3:]))
|
||||
|
||||
def findSuffix(s, o):
|
||||
n = min(len(s.cmds), len(o.cmds))
|
||||
for i in range(0,n):
|
||||
if (s.cmds[-1-i] != o.cmds[-1-i]):
|
||||
if i > 0:
|
||||
return s.cmds[-i:]
|
||||
return []
|
||||
return s.cmds[-n:]
|
||||
|
||||
def genCode(s, grouped):
|
||||
global outFile, allLabels, generatedLabels
|
||||
print("genCode %s, trunc=%r" % (s.label, s.truncPos))
|
||||
first = grouped
|
||||
for i in range(len(s.cmds)):
|
||||
if i == s.truncPos:
|
||||
if grouped:
|
||||
if s.truncPos == 0:
|
||||
# Routine is entirely duplicated elsewhere: no need to do anything
|
||||
return
|
||||
if s.refs == 1:
|
||||
# singletons aren't generated near their target
|
||||
outFile.write(" jmp %s\n" % s.truncLabel)
|
||||
else:
|
||||
# non-singletons are generated near their target
|
||||
outFile.write(" bra %s\n" % s.truncLabel)
|
||||
else:
|
||||
outFile.write(" jsr %s\n" % s.truncLabel)
|
||||
break
|
||||
label = calcLabel(s.cmds[i:])
|
||||
if label not in generatedLabels and (first or label in allLabels):
|
||||
outFile.write("%s:\n" % label)
|
||||
generatedLabels.add(label)
|
||||
first = False
|
||||
c = s.cmds[i]
|
||||
if c == 'L':
|
||||
outFile.write(" lda (pshape),y\n")
|
||||
outFile.write(" iny\n")
|
||||
elif c == '<':
|
||||
outFile.write(" lsr\n")
|
||||
else:
|
||||
outFile.write(" sta %s*15+rowblit,x\n" % c)
|
||||
else:
|
||||
if grouped:
|
||||
outFile.write(" rts\n")
|
||||
if grouped:
|
||||
outFile.write("\n")
|
||||
outFile.flush()
|
||||
s.generated = True
|
||||
|
||||
def makeCmds(dstHeight):
|
||||
global allHeights, dstHeights
|
||||
|
||||
dstHeights.add(dstHeight)
|
||||
|
||||
# Figure out which texture mip-map level to use
|
||||
srcHeight = 2
|
||||
texBase = 0
|
||||
while srcHeight < textureHeight and srcHeight*2 <= dstHeight:
|
||||
texBase += srcHeight
|
||||
srcHeight *= 2
|
||||
|
||||
y0 = (screenHeight/2) - (dstHeight/2)
|
||||
y1 = (screenHeight/2) + (dstHeight/2)
|
||||
b = 0
|
||||
r = 0
|
||||
cmds = []
|
||||
|
||||
texOff = texBase
|
||||
if y0 < 0:
|
||||
texOff += int((-y0/2) * srcHeight / dstHeight)
|
||||
y0 = max(0, y0)
|
||||
y1 = max(screenHeight/2, y1)
|
||||
|
||||
for y in range(y0, y1):
|
||||
if b == 0:
|
||||
cmds.append("L")
|
||||
b = 1
|
||||
cmds.append(y)
|
||||
if y < y1-1:
|
||||
r += srcHeight
|
||||
if r >= dstHeight:
|
||||
r -= dstHeight
|
||||
if b == 1:
|
||||
cmds.append("<")
|
||||
b = 2
|
||||
else:
|
||||
b = 0
|
||||
assert r < dstHeight, "wrapped twice?"
|
||||
|
||||
print("%d ->\t%d:\t%s" % (srcHeight, dstHeight, calcKey(cmds)))
|
||||
allHeights.append((srcHeight, dstHeight, texOff, segment(cmds)))
|
||||
|
||||
def flushSeg(seg):
|
||||
global allSegs
|
||||
if seg.key in allSegs:
|
||||
allSegs[seg.key].refs += 1
|
||||
else:
|
||||
allSegs[seg.key] = seg
|
||||
fs = seg.finalSuffix()
|
||||
if not fs in finalSuffixMap:
|
||||
finalSuffixMap[fs] = set()
|
||||
finalSuffixMap[fs].add(seg.key)
|
||||
return seg.key
|
||||
|
||||
def segment(cmds):
|
||||
global rowInterval
|
||||
segs = []
|
||||
seg = []
|
||||
n = 0
|
||||
for c in cmds:
|
||||
seg.append(c)
|
||||
if c != "L" and c != "<":
|
||||
n += c
|
||||
if (c % rowInterval) == (rowInterval-1):
|
||||
segs.append(flushSeg(Segment(seg)))
|
||||
seg = []
|
||||
if len(seg) > 0:
|
||||
segs.append(flushSeg(Segment(seg)))
|
||||
return segs
|
||||
|
||||
for dstHeight in range(2,128,2):
|
||||
makeCmds(dstHeight)
|
||||
for dstHeight in range(128,192,4):
|
||||
makeCmds(dstHeight)
|
||||
for dstHeight in range(192,256,8):
|
||||
makeCmds(dstHeight)
|
||||
|
||||
# Create the jump tables for sizes 0..127, 128..255
|
||||
dstHeight = 0
|
||||
for h in range(0, 256, 2):
|
||||
if h == 0:
|
||||
outFile.write("expand_vec1:\n")
|
||||
elif h == 128:
|
||||
outFile.write("\nexpand_vec2:\n")
|
||||
if h in dstHeights:
|
||||
dstHeight = h
|
||||
outFile.write(" .addr expand_%d\n" % dstHeight)
|
||||
outFile.write("\n")
|
||||
|
||||
# Let's optimize.
|
||||
segsToOpt = copy.copy(allSegs)
|
||||
while len(segsToOpt) > 0:
|
||||
|
||||
# Find the segment that would gain the most by being referenced.
|
||||
maxGain = 0
|
||||
bestDeps = None
|
||||
bestSeg = None
|
||||
for k1 in sorted(segsToOpt):
|
||||
deps = []
|
||||
gain = -1
|
||||
for k2 in sorted(finalSuffixMap[segsToOpt[k1].finalSuffix()]):
|
||||
if k2 == k1: continue # don't reference ourselves
|
||||
if not k2 in segsToOpt: continue # skip already-finished segs
|
||||
suf = segsToOpt[k1].findSuffix(segsToOpt[k2])
|
||||
if len(suf) < 3: continue
|
||||
sufKey = calcKey(suf)
|
||||
assert segsToOpt[k1].key.endswith(sufKey), \
|
||||
"suf %s vs %s = bad %s" % (segsToOpt[k1].key, segsToOpt[k2].key, sufKey)
|
||||
assert segsToOpt[k2].key.endswith(sufKey), \
|
||||
"suf %s vs %s = bad %s" % (segsToOpt[k1].key, segsToOpt[k2].key, sufKey)
|
||||
deps.append(k2)
|
||||
gain += calcSpace(suf) - 2 # 2 for branch
|
||||
if gain > maxGain:
|
||||
maxGain = gain
|
||||
bestDeps = deps
|
||||
bestSeg = k1
|
||||
if bestSeg is None:
|
||||
break
|
||||
|
||||
# Display what we found
|
||||
print("Seg: %s\tgain %d" % (bestSeg, maxGain))
|
||||
for dep in bestDeps:
|
||||
suf = segsToOpt[bestSeg].findSuffix(segsToOpt[dep])
|
||||
print("\t%d\t%s" % (calcSpace(suf)-2, segsToOpt[dep].key))
|
||||
|
||||
# Generate code for the non-singletons
|
||||
segsToOpt[bestSeg].refs += 1
|
||||
for dep in bestDeps:
|
||||
suf = segsToOpt[bestSeg].findSuffix(segsToOpt[dep])
|
||||
segsToOpt[dep].truncPos = len(segsToOpt[dep].cmds) - len(suf)
|
||||
segsToOpt[dep].truncLabel = calcLabel(suf)
|
||||
allLabels.add(segsToOpt[dep].truncLabel)
|
||||
for i in range(0, len(bestDeps)/2):
|
||||
if segsToOpt[bestDeps[i]].refs > 1:
|
||||
segsToOpt[bestDeps[i]].genCode(True)
|
||||
segsToOpt[bestSeg].genCode(True)
|
||||
for i in range(len(bestDeps)/2, len(bestDeps)):
|
||||
if segsToOpt[bestDeps[i]].refs > 1:
|
||||
segsToOpt[bestDeps[i]].genCode(True)
|
||||
|
||||
# Remove these segments from optimization consideration
|
||||
del segsToOpt[bestSeg]
|
||||
for dep in bestDeps:
|
||||
del segsToOpt[dep]
|
||||
|
||||
# Now generate the controlling code
|
||||
for (srcHeight, dstHeight, texOff, segs) in allHeights:
|
||||
outFile.write("; Produce %d rows from %d rows\n" % (dstHeight, srcHeight))
|
||||
outFile.write("expand_%d:\n" % dstHeight)
|
||||
outFile.write(" ldy #%d\n" % texOff)
|
||||
for i in range(len(segs)):
|
||||
seg = allSegs[segs[i]]
|
||||
if seg.refs == 1 and not(seg.generated):
|
||||
seg.genCode(False)
|
||||
if i == len(segs)-1:
|
||||
# Special case for generating zero-size
|
||||
if "expand_0" not in generatedLabels:
|
||||
outFile.write("expand_0:\n")
|
||||
generatedLabels.add("expand_0")
|
||||
outFile.write(" rts\n")
|
||||
else:
|
||||
outFile.write(" %s %s\n" % ("jsr" if i < len(segs)-1 else "jmp", calcLabel(seg.cmds)))
|
||||
outFile.write("\n")
|
||||
|
||||
# Generate the misc segments missed earlier
|
||||
for seg in allSegs.itervalues():
|
||||
if not seg.generated:
|
||||
seg.genCode(True)
|
||||
|
||||
outFile.close()
|
66
Platform/Apple/virtual/src/raycast/javascript/intcast.htm
Executable file
66
Platform/Apple/virtual/src/raycast/javascript/intcast.htm
Executable file
@ -0,0 +1,66 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>JScript Lawless Legends Testbed</title>
|
||||
<!--
|
||||
/*
|
||||
* Example code from the article at: http://dev.opera.com/articles/view/creating-pseudo-3d-games-with-html-5-can-1/
|
||||
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
|
||||
* The code is available to use under the terms of the MIT License
|
||||
* [http://www.opensource.org/licenses/mit-license.php]
|
||||
*/
|
||||
-->
|
||||
|
||||
<style type="text/css">
|
||||
|
||||
div#minimapcontainer {
|
||||
|
||||
}
|
||||
canvas#minimap {
|
||||
position : absolute;
|
||||
}
|
||||
canvas#minimapobjects {
|
||||
position : absolute;
|
||||
}
|
||||
|
||||
div#floor {
|
||||
position : absolute;
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
background-color : rgb(128,128,128);
|
||||
}
|
||||
|
||||
div#ceiling {
|
||||
position : absolute;
|
||||
width : 100%;
|
||||
height : 50%;
|
||||
background-color : rgb(96,96,96);
|
||||
}
|
||||
|
||||
|
||||
#screen {
|
||||
position : relative;
|
||||
width : 504px;
|
||||
height : 512px;
|
||||
border : 1px solid black;
|
||||
overflow : hidden;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript" src="intcast.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="screen">
|
||||
<div id="floor"></div>
|
||||
<div id="ceiling"></div>
|
||||
</div>
|
||||
|
||||
<div id="minimapcontainer">
|
||||
<canvas id="minimap"></canvas>
|
||||
<canvas id="minimapobjects"></canvas>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
916
Platform/Apple/virtual/src/raycast/javascript/intcast.js
Normal file
916
Platform/Apple/virtual/src/raycast/javascript/intcast.js
Normal file
@ -0,0 +1,916 @@
|
||||
|
||||
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,0,0,0,0,3,0,0,2,0,3],
|
||||
[1,0,0,0,0,0,0,1,0,0,3,0,2],
|
||||
[3,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,2,0,2,0,0,0,0,0,0,4],
|
||||
[1,0,0,3,0,0,0,3,0,0,3,0,1],
|
||||
[3,0,0,1,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]
|
||||
];
|
||||
|
||||
var player = {
|
||||
x : 2.5, // current x, y position
|
||||
y : 2.5,
|
||||
dir : 0, // the direction that the player is turning, either -1 for left or 1 for right.
|
||||
angleNum : 0, // the current angle of rotation
|
||||
speed : 0, // is the playing moving forward (speed = 1) or backwards (speed = -1).
|
||||
moveSpeed : 0.5, // how far (in map units) does the player move each step/update
|
||||
rotSpeed : 22.5 * Math.PI / 180 // how much does the player rotate each step/update (in radians)
|
||||
}
|
||||
|
||||
var options = 0;
|
||||
|
||||
var debugRay = null; /* Debugging info printed about this ray num, or null for none */
|
||||
|
||||
var maxAngleNum = 16;
|
||||
|
||||
var mapWidth = 0;
|
||||
var mapHeight = 0;
|
||||
|
||||
var miniMapScale = 8;
|
||||
|
||||
var screenWidth = 504;
|
||||
var screenHeight = 512;
|
||||
|
||||
var stripWidth = 1;
|
||||
var fov = 45 * Math.PI / 180;
|
||||
|
||||
var numRays = Math.ceil(screenWidth / stripWidth);
|
||||
var fovHalf = fov / 2;
|
||||
|
||||
var viewDist = (screenWidth/2) / Math.tan((fov / 2));
|
||||
|
||||
var twoPI = Math.PI * 2;
|
||||
|
||||
var numTextures = 4;
|
||||
|
||||
// Tables
|
||||
var precastData = [];
|
||||
var tbl_log2_b_b = null;
|
||||
var tbl_pow2_b_b = null;
|
||||
var tbl_log2_w_w = null;
|
||||
var tbl_pow2_w_w = null;
|
||||
|
||||
function init() {
|
||||
|
||||
mapWidth = map[0].length;
|
||||
mapHeight = map.length;
|
||||
|
||||
bindKeys();
|
||||
|
||||
initScreen();
|
||||
initCast();
|
||||
|
||||
drawMiniMap();
|
||||
|
||||
gameCycle();
|
||||
}
|
||||
|
||||
var screenStrips = [];
|
||||
|
||||
function initScreen() {
|
||||
|
||||
var screen = $("screen");
|
||||
|
||||
for (var i=0;i<screenWidth;i+=stripWidth) {
|
||||
var strip = dc("div");
|
||||
strip.style.position = "absolute";
|
||||
strip.style.left = i + "px";
|
||||
strip.style.width = stripWidth+"px";
|
||||
strip.style.height = "0px";
|
||||
strip.style.overflow = "hidden";
|
||||
|
||||
strip.style.backgroundColor = "magenta";
|
||||
|
||||
var img = new Image();
|
||||
img.src = "walls.png";
|
||||
img.style.position = "absolute";
|
||||
img.style.left = "0px";
|
||||
|
||||
strip.appendChild(img);
|
||||
strip.img = img; // assign the image to a property on the strip element so we have easy access to the image later
|
||||
|
||||
screenStrips.push(strip);
|
||||
screen.appendChild(strip);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initCast()
|
||||
{
|
||||
var i;
|
||||
|
||||
console.log("Initializing cast data.");
|
||||
precastData = [];
|
||||
for (var angleNum = 0; angleNum < maxAngleNum; angleNum++)
|
||||
{
|
||||
var angleData = [];
|
||||
for (var x = 0; x < 63; x++)
|
||||
angleData.push(prepCast(angleNum, x));
|
||||
precastData.push(angleData);
|
||||
}
|
||||
|
||||
// Tables for 8-bit log2 and pow2
|
||||
tbl_log2_b_b = [];
|
||||
tbl_pow2_b_b = [];
|
||||
function o2_log2_b_b(n) {
|
||||
assert(n >= 4 && n <= 255, "n out of range for log2_b_b");
|
||||
var ln = Math.log(n) / Math.log(2);
|
||||
assert(ln >= 2 && ln < 8, "ln out of range");
|
||||
return ubyte(Math.max(0,Math.min(127,Math.round((ln-2) * 128 / 6))));
|
||||
}
|
||||
function o2_exp2_b_w(n) {
|
||||
var ln = (n * 6 / 128) + 4;
|
||||
return uword(Math.pow(2, ln));
|
||||
}
|
||||
tbl_log2_b_b[0] = tbl_log2_b_b[1] = tbl_log2_b_b[2] = tbl_log2_b_b[3] = 0;
|
||||
for (i=4; i<256; i++)
|
||||
tbl_log2_b_b[i] = o2_log2_b_b(i);
|
||||
for (i=0; i<256; i++)
|
||||
tbl_pow2_b_b[i] = ubyte(o2_exp2_b_w(i) >> 8);
|
||||
|
||||
// Tables for 16-bit log2 and pow2. These handle the mantissa only, and the
|
||||
// exponent is handled by bit shifting and counting.
|
||||
//
|
||||
tbl_log2_w_w = [];
|
||||
for (i=0; i<256; i++)
|
||||
tbl_log2_w_w[i] = ubyte(Math.log((i+256) / 256) / Math.log(2) * 256);
|
||||
tbl_pow2_w_w = [];
|
||||
for (i=0; i<256; i++)
|
||||
tbl_pow2_w_w[i] = ubyte((Math.pow(2, i / 255) - 1) * 255);
|
||||
|
||||
console.log("Done.");
|
||||
}
|
||||
|
||||
function prepCast(angleNum, x)
|
||||
{
|
||||
// Using the view angle, compute the direction vector
|
||||
var angle = angleNum * player.rotSpeed;
|
||||
var dirX = Math.cos(angle);
|
||||
var dirY = Math.sin(angle);
|
||||
|
||||
// Compute the camera plane, which is perpendicular to the direction vector
|
||||
var planeX = -Math.sin(angle) * 0.5;
|
||||
var planeY = Math.cos(angle) * 0.5;
|
||||
|
||||
// Calculate ray position and direction.
|
||||
// Note that normalizing this vector results in fisheye, so don't do it!
|
||||
var cameraX = 2 * x / 63 - 1; // x-coordinate in camera space
|
||||
var rayDirX = dirX + planeX * cameraX;
|
||||
var rayDirY = dirY + planeY * cameraX;
|
||||
|
||||
// Length of ray from one x or y-side to next x or y-side
|
||||
var deltaDistX = Math.sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
|
||||
var deltaDistY = Math.sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
|
||||
|
||||
deltaDistX = Math.min(255, deltaDistX/4);
|
||||
deltaDistY = Math.min(255, deltaDistY/4);
|
||||
|
||||
// Get as much accuracy as we can into 8 bits
|
||||
var bestDiff = 99999;
|
||||
var bestNumer;
|
||||
var bestDenom;
|
||||
for (var denom = 1; denom < 128; denom++) {
|
||||
var numer = Math.floor(deltaDistX * denom / deltaDistY);
|
||||
var diff = Math.abs((numer/denom) - (deltaDistX/deltaDistY));
|
||||
if (numer >= 0 && numer < 128 && diff <= bestDiff) {
|
||||
bestDiff = diff;
|
||||
bestNumer = numer;
|
||||
bestDenom = denom;
|
||||
}
|
||||
}
|
||||
|
||||
// Rarely-needed fallback
|
||||
if (bestNumer === undefined)
|
||||
{
|
||||
bestNumer = deltaDistX;
|
||||
bestDenom = deltaDistY;
|
||||
while (bestNumer < 64 && bestDenom < 64) {
|
||||
bestNumer *= 2;
|
||||
bestDenom *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
deltaDistX = ubyte(bestNumer);
|
||||
deltaDistY = ubyte(bestDenom);
|
||||
|
||||
// The math produces numbers a bit bigger than 1; we need to scale them down evenly.
|
||||
// (see fisheye note above).
|
||||
//
|
||||
var scaleFactor = 1.116;
|
||||
|
||||
rayDirX = (rayDirX / scaleFactor) * 128;
|
||||
rayDirY = (rayDirY / scaleFactor) * 128;
|
||||
|
||||
rayDirX = ubyte(Math.round(rayDirX) & 0xFF);
|
||||
rayDirY = ubyte(Math.round(rayDirY) & 0xFF);
|
||||
|
||||
// Encapsulate the pre-computed numbers
|
||||
return { rayDirX: rayDirX, rayDirY: rayDirY,
|
||||
deltaDistX: deltaDistX, deltaDistY: deltaDistY };
|
||||
}
|
||||
|
||||
function printPrecast() {
|
||||
for (var i=0; i<maxAngleNum; i++) {
|
||||
console.log("precast_" + i + ":");
|
||||
for (var j=0; j<63; j++) {
|
||||
console.log(" .byte $" + byteToHex(precastData[i][j].rayDirX) + "," +
|
||||
"$" + byteToHex(precastData[i][j].rayDirY) + "," +
|
||||
"$" + byteToHex(precastData[i][j].deltaDistX) + "," +
|
||||
"$" + byteToHex(precastData[i][j].deltaDistY));
|
||||
}
|
||||
console.log(" .res 4 ; to bring it up to 256 bytes per angle");
|
||||
}
|
||||
}
|
||||
|
||||
function printTbl(arr) {
|
||||
var line = null;
|
||||
for (var i in arr) {
|
||||
if (line == null)
|
||||
line = " .byte ";
|
||||
else
|
||||
line += ",";
|
||||
line += "$" + byteToHex(arr[i]);
|
||||
if ((i%16) == 15) {
|
||||
console.log(line);
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
if (line != null)
|
||||
console.log(line);
|
||||
}
|
||||
|
||||
// bind keyboard events to game functions (movement, etc)
|
||||
function bindKeys() {
|
||||
|
||||
document.onkeydown = function(e) {
|
||||
e = e || window.event;
|
||||
|
||||
switch (e.keyCode) { // which key was pressed?
|
||||
|
||||
case 38: // up, move player forward, ie. increase speed
|
||||
player.speed = 1;
|
||||
break;
|
||||
|
||||
case 40: // down, move player backward, set negative speed
|
||||
player.speed = -1;
|
||||
break;
|
||||
|
||||
case 37: // left, rotate player left
|
||||
player.dir = -1;
|
||||
break;
|
||||
|
||||
case 39: // right, rotate player right
|
||||
player.dir = 1;
|
||||
break;
|
||||
|
||||
case 49: // '1': toggle option 1
|
||||
options ^= 1;
|
||||
console.log("options: " + options);
|
||||
initCast();
|
||||
break;
|
||||
|
||||
case 50: // '2': toggle option 2
|
||||
options ^= 2;
|
||||
console.log("options: " + options);
|
||||
initCast();
|
||||
break;
|
||||
|
||||
case 51: // '4': toggle option 3
|
||||
options ^= 4;
|
||||
console.log("options: " + options);
|
||||
initCast();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.onkeyup = function(e) {
|
||||
e = e || window.event;
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
case 40:
|
||||
player.speed = 0; // stop the player movement when up/down key is released
|
||||
break;
|
||||
case 37:
|
||||
case 39:
|
||||
player.dir = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gameCycle() {
|
||||
|
||||
move();
|
||||
|
||||
updateMiniMap();
|
||||
|
||||
castRays();
|
||||
|
||||
setTimeout(gameCycle,1000/30); // aim for 30 FPS
|
||||
|
||||
player.speed = 0;
|
||||
player.dir = 0;
|
||||
}
|
||||
|
||||
function playerAngle()
|
||||
{
|
||||
return player.angleNum * player.rotSpeed;
|
||||
}
|
||||
|
||||
var prevX = -1;
|
||||
var prevY = -1;
|
||||
var prevAngleNum = -1;
|
||||
var prevOptions = -1;
|
||||
|
||||
function castRays(force)
|
||||
{
|
||||
// If we're already showing this location and angle, no need to re-do it.
|
||||
if (!force &&
|
||||
player.x == prevX &&
|
||||
player.y == prevY &&
|
||||
player.angleNum == prevAngleNum &&
|
||||
options == prevOptions)
|
||||
return;
|
||||
|
||||
// Cast all the rays and record the data
|
||||
lineData = [];
|
||||
var rayNum;
|
||||
for (rayNum = 0; rayNum < 63; rayNum++) {
|
||||
data = intCast(rayNum);
|
||||
// For display on the PC, expand pixels 8x
|
||||
data.height *= 8;
|
||||
for (var i=0; i<8; i++)
|
||||
lineData.push(data);
|
||||
}
|
||||
|
||||
// Save position and angle (so we can avoid re-rendering if they don't change)
|
||||
prevX = player.x;
|
||||
prevY = player.y;
|
||||
prevAngleNum = player.angleNum;
|
||||
prevOptions = options;
|
||||
|
||||
// Draw all the rays
|
||||
for (rayNum in lineData)
|
||||
drawStrip(rayNum, lineData[rayNum]);
|
||||
}
|
||||
|
||||
function assert(flg, msg) {
|
||||
if (!flg) {
|
||||
console.log("Assertion failed: " + msg);
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
|
||||
function byteToHex(d) {
|
||||
assert(d >= 0 && d <= 255, "byte out of range");
|
||||
var hex = Number(d).toString(16).toUpperCase();
|
||||
return "00".substr(0, 2 - hex.length) + hex;
|
||||
}
|
||||
|
||||
// Convert a float to an unsigned byte by truncation
|
||||
function ubyte(n) {
|
||||
assert(n >=0 && n < 256, "ubyte out of range");
|
||||
return parseInt(n);
|
||||
}
|
||||
|
||||
// Convert a float to an signed byte by truncation
|
||||
function sbyte(n) {
|
||||
assert(n >= -128 && n < 128, "sbyte out of range");
|
||||
return parseInt(n);
|
||||
}
|
||||
|
||||
// Convert a float to an unsigned word by truncation
|
||||
function uword(n) {
|
||||
assert(n >=0 && n < 65536, "ubyte out of range");
|
||||
return parseInt(n);
|
||||
}
|
||||
|
||||
// Convert a float to an signed word by truncation
|
||||
function sword(n) {
|
||||
assert(n >= -32768 && n < 32768, "sbyte out of range");
|
||||
return parseInt(n);
|
||||
}
|
||||
|
||||
// Floor a 8.8 fixed number to an 8 bit int
|
||||
function floor_w_b(num) {
|
||||
var ret = num >> 8;
|
||||
if (ret < 0 && (num&0xFF))
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Add two bytes, throwing away any overflow.
|
||||
function uadd_bb_b(n1, n2) {
|
||||
assert(n1 >= 0 && n2 >= 0, "numbers for uadd_bb_b must be positive");
|
||||
assert(n1 <= 255 && n2 <= 255, "numbers for uadd_bb_b must be valid bytes");
|
||||
return (n1 + n2) & 0xFF;
|
||||
}
|
||||
|
||||
// See if n1 < n2, unsigned
|
||||
function uless_bb(n1, n2) {
|
||||
assert(n1 >= 0 && n2 >= 0, "numbers for uless_bb must be positive");
|
||||
assert(n1 <= 255 && n2 <= 255, "numbers for uless_bb must be valid bytes");
|
||||
return n1 < n2;
|
||||
}
|
||||
|
||||
function log2(n, bits) {
|
||||
if (n < 1)
|
||||
return 0;
|
||||
var ln = Math.log(n) / Math.log(2);
|
||||
return Math.round(ln * (1<<bits));
|
||||
}
|
||||
|
||||
function pow2(n, bits) {
|
||||
return Math.pow(2, n / (1<<bits));
|
||||
}
|
||||
|
||||
// Fast log2: take ubyte and produce ubyte 3.5 fixed-point: base-2 logarithm
|
||||
function log2_b_b(n) {
|
||||
return tbl_log2_b_b[n];
|
||||
}
|
||||
|
||||
// Fast pow2: take ubyte 5.3 fixed-point logarithm, produce ubyte: 2^n
|
||||
function pow2_b_b(n) {
|
||||
return tbl_pow2_b_b[n];
|
||||
}
|
||||
|
||||
// Multiply two unsigned bytes, and take the high byte of the result.
|
||||
function umul_bb_b(n1, n2)
|
||||
{
|
||||
assert(n1 >= 0 && n2 >= 0, "numbers for umul_bb_b must be positive");
|
||||
assert(n1 <= 255 && n2 <= 255, "numbers for umul_bb_b must be valid bytes");
|
||||
|
||||
// Use table-based pow(log) for super-speed.
|
||||
if (n1 < 4 || n2 < 4) // fast and easy to compute; makes tables smaller
|
||||
return ubyte((n1*n2) >> 8);
|
||||
return pow2_b_b(log2_b_b(n1) + log2_b_b(n2));
|
||||
|
||||
// non-log code for reference:
|
||||
//return ubyte((n1*n2) >> 8);
|
||||
}
|
||||
|
||||
// Multiply two unsigned 8.8 numbers, resulting in 8.8
|
||||
function umul_ww_w(n1, n2) {
|
||||
assert(n1 >= 0 && n2 >= 0, "numbers for umul_ww_w must be positive");
|
||||
assert(n1 <= 65535 && n2 <= 65535, "numbers for umul_ww_w must be valid words");
|
||||
return uword((n1*n2) >> 8);
|
||||
}
|
||||
|
||||
// Unsigned mult, 16 by 8 bit, taking only lower 8 bits of result.
|
||||
function umul_wb_b(n1, n2) {
|
||||
assert(n1 >= 0 && n1 <= 65535, "n1 for umul_wb_b must be valid unsigned word");
|
||||
assert(n2 >= 0 && n2 <= 255, "n2 for umul_wb_b must be valid unsigned byte");
|
||||
return ubyte(((n1*n2) >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
function abs_w_w(n) {
|
||||
return uword((n<0) ? -n : n);
|
||||
}
|
||||
|
||||
// Table-based high precision log2 - 16 bit to 16 bit
|
||||
function log2_w_w(n) {
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
// Calculate the exponent, and leave mantissa in n.
|
||||
var exp = 8;
|
||||
while (n >= 512) {
|
||||
exp++;
|
||||
n >>= 1;
|
||||
}
|
||||
while (n < 256) {
|
||||
exp--;
|
||||
n <<= 1;
|
||||
}
|
||||
|
||||
// Combine to form the result
|
||||
return (exp << 8) | tbl_log2_w_w[n & 0xFF];
|
||||
}
|
||||
|
||||
// Table-based high precision pow2 - 16 bit to 16 bit
|
||||
function pow2_w_w(n)
|
||||
{
|
||||
var exp = n >> 8;
|
||||
var result = tbl_pow2_w_w[n & 0xFF] + 256;
|
||||
if (exp > 8)
|
||||
result <<= (exp-8);
|
||||
else if (exp < 8)
|
||||
result >>= (8-exp);
|
||||
return result;
|
||||
}
|
||||
|
||||
function wallCalc(dist, bDir1, bDir2, bStep2)
|
||||
{
|
||||
// Calculate perpendicular distance. Use logarithms for super-speed.
|
||||
dist = abs_w_w(dist);
|
||||
var diff = log2_w_w(dist) - log2_w_w(bDir1);
|
||||
|
||||
// Calculate texture coordinate
|
||||
var sum = diff + log2_w_w(bDir2);
|
||||
bWallX = pow2_w_w(sum) & 0xFF;
|
||||
if (bStep2 < 0)
|
||||
bWallX ^= 0xFF;
|
||||
|
||||
// Calculate line height
|
||||
sum = log2_w_w(64) - diff;
|
||||
lineHeight = uword(pow2_w_w(sum));
|
||||
return [lineHeight, bWallX]
|
||||
|
||||
/* No-log algorithm
|
||||
var wInvDir = uword(65536 / bDir1);
|
||||
var dist2 = umul_ww_w(dist, wInvDir);
|
||||
var bWallX = umul_wb_b(dist2, bDir2);
|
||||
if (bStep2 < 0)
|
||||
bWallX = 255 - bWallX;
|
||||
var lineHeight = Math.floor(63 * 256 / dist2);
|
||||
return [lineHeight, bWallX]
|
||||
*/
|
||||
}
|
||||
|
||||
// Let's try this with strictly integers. This should be the final
|
||||
// step before the Apple II code.
|
||||
//
|
||||
function intCast(x)
|
||||
{
|
||||
var data = precastData[player.angleNum][x];
|
||||
|
||||
// Calculate ray position and direction
|
||||
var wRayPosX = sword(player.x * 256);
|
||||
var wRayPosY = sword(player.y * 256);
|
||||
var bStepX = 1;
|
||||
var bStepY = 1;
|
||||
var bRayDirX = data.rayDirX;
|
||||
var bRayDirY = data.rayDirY;
|
||||
|
||||
if (bRayDirX & 0x80) {
|
||||
bStepX = -1;
|
||||
bRayDirX ^= 0xFF;
|
||||
}
|
||||
|
||||
if (bRayDirY & 0x80) {
|
||||
bStepY = -1;
|
||||
bRayDirY ^= 0xFF;
|
||||
}
|
||||
|
||||
bRayDirX <<= 1;
|
||||
bRayDirY <<= 1;
|
||||
|
||||
// Which box of the map we're in
|
||||
var bMapX = floor_w_b(wRayPosX);
|
||||
var bMapY = floor_w_b(wRayPosY);
|
||||
|
||||
// Length of ray from current position to next x or y-side
|
||||
var bSideDistX;
|
||||
var bSideDistY;
|
||||
|
||||
// Length of ray from one x or y-side to next x or y-side
|
||||
var bDeltaDistX = data.deltaDistX;
|
||||
var bDeltaDistY = data.deltaDistY;
|
||||
|
||||
if (x == debugRay) {
|
||||
console.log("intCast: ray=" + x +
|
||||
", bDeltaDistX=" + bDeltaDistX +
|
||||
", bDeltaDistY=" + bDeltaDistY);
|
||||
}
|
||||
|
||||
// Calculate step and initial sideDist
|
||||
bSideDistX = wRayPosX & 0xFF;
|
||||
if (bStepX >= 0)
|
||||
bSideDistX ^= 0xFF;
|
||||
bSideDistX = umul_bb_b(bSideDistX, bDeltaDistX);
|
||||
|
||||
bSideDistY = wRayPosY & 0xFF;
|
||||
if (bStepY >= 0)
|
||||
bSideDistY ^= 0xFF;
|
||||
bSideDistY = umul_bb_b(bSideDistY, bDeltaDistY);
|
||||
|
||||
if (x == debugRay) {
|
||||
console.log(" initial sideDistX=" + bSideDistX +
|
||||
", sideDistY=" + bSideDistY);
|
||||
}
|
||||
|
||||
// Distance to wall, and texture coordinate on the wall
|
||||
var lineHeight;
|
||||
var bWallX;
|
||||
|
||||
// Perform DDA
|
||||
while (true)
|
||||
{
|
||||
// Jump to next map square in x-direction, OR in y-direction
|
||||
if (uless_bb(bSideDistX, bSideDistY)) {
|
||||
bSideDistY -= bSideDistX;
|
||||
bSideDistX = bDeltaDistX;
|
||||
bMapX += bStepX;
|
||||
if (x == debugRay) {
|
||||
console.log(" side0, mapX=" + bMapX + ", mapY=" + bMapY +
|
||||
", sideDistX=" + bSideDistX +
|
||||
", sideDistY=" + bSideDistY);
|
||||
}
|
||||
if (map[bMapY][bMapX] > 0) {
|
||||
if (x == debugRay)
|
||||
console.log(" hit side 0");
|
||||
var wPerpWallDist = (bMapX<<8) - wRayPosX;
|
||||
if (bStepX < 0)
|
||||
wPerpWallDist += 256;
|
||||
nums = wallCalc(wPerpWallDist, bRayDirX, bRayDirY, bStepY);
|
||||
lineHeight = nums[0]
|
||||
bWallX = nums[1]
|
||||
bWallX = uadd_bb_b(bWallX, wRayPosY & 0xFF);
|
||||
if (bStepX >= 0)
|
||||
bWallX ^= 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bSideDistX -= bSideDistY;
|
||||
bSideDistY = bDeltaDistY;
|
||||
bMapY += bStepY;
|
||||
if (x == debugRay) {
|
||||
console.log(" side1, mapX=" + bMapX + ", mapY=" + bMapY +
|
||||
", sideDistX=" + bSideDistX +
|
||||
", sideDistY=" + bSideDistY);
|
||||
}
|
||||
if (map[bMapY][bMapX] > 0) {
|
||||
if (x == debugRay)
|
||||
console.log(" hit side 1");
|
||||
var wPerpWallDist = (bMapY<<8) - wRayPosY;
|
||||
if (bStepY < 0)
|
||||
wPerpWallDist += 256;
|
||||
nums = wallCalc(wPerpWallDist, bRayDirY, bRayDirX, bStepX);
|
||||
lineHeight = nums[0];
|
||||
bWallX = nums[1];
|
||||
bWallX = uadd_bb_b(bWallX, wRayPosX & 0xFF);
|
||||
if (bStepY < 0)
|
||||
bWallX ^= 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x == debugRay) {
|
||||
console.log(" perpWallDist=" + wPerpWallDist.toString() +
|
||||
", lineHeight=" + lineHeight.toString());
|
||||
}
|
||||
|
||||
// Wrap it all in a nice package.
|
||||
return { wallType: map[bMapY][bMapX],
|
||||
textureX: bWallX / 256.0,
|
||||
height: lineHeight };
|
||||
}
|
||||
|
||||
function newCast(x)
|
||||
{
|
||||
var data = precastData[player.angleNum][x];
|
||||
|
||||
// Calculate ray position and direction
|
||||
var rayPosX = player.x;
|
||||
var rayPosY = player.y;
|
||||
var rayDirX = data.rayDirX;
|
||||
var rayDirY = data.rayDirY;
|
||||
|
||||
// Which box of the map we're in
|
||||
var mapX = Math.floor(rayPosX);
|
||||
var mapY = Math.floor(rayPosY);
|
||||
|
||||
// Length of ray from current position to next x or y-side
|
||||
var sideDistX;
|
||||
var sideDistY;
|
||||
|
||||
// Length of ray from one x or y-side to next x or y-side
|
||||
var deltaDistX = data.deltaDistX;
|
||||
var deltaDistY = data.deltaDistY;
|
||||
|
||||
if (x == debugRay) {
|
||||
console.log("newCast: ray=" + x +
|
||||
", deltaDistX=" + deltaDistX.toString() +
|
||||
", deltaDistY=" + deltaDistY.toString());
|
||||
}
|
||||
|
||||
// What direction to step in x or y-direction (either +1 or -1)
|
||||
var stepX;
|
||||
var stepY;
|
||||
|
||||
// Calculate step and initial sideDist
|
||||
if (rayDirX < 0) {
|
||||
stepX = -1;
|
||||
sideDistX = (rayPosX % 1) * deltaDistX;
|
||||
}
|
||||
else {
|
||||
stepX = 1;
|
||||
sideDistX = (1.0 - (rayPosX % 1)) * deltaDistX;
|
||||
}
|
||||
|
||||
if (rayDirY < 0) {
|
||||
stepY = -1;
|
||||
sideDistY = (rayPosY % 1) * deltaDistY;
|
||||
}
|
||||
else {
|
||||
stepY = 1;
|
||||
sideDistY = (1.0 - (rayPosY % 1)) * deltaDistY;
|
||||
}
|
||||
|
||||
// Distance to wall, and texture coordinate on the wall
|
||||
var perpWallDist = 0;
|
||||
var wallX;
|
||||
|
||||
// Perform DDA
|
||||
while (mapX >= 0 && mapX < mapWidth && mapY >= 0 && mapY < mapHeight)
|
||||
{
|
||||
// Jump to next map square in x-direction, OR in y-direction
|
||||
if (sideDistX < sideDistY) {
|
||||
sideDistX += deltaDistX;
|
||||
mapX += stepX;
|
||||
if (x == debugRay) {
|
||||
console.log(" side0, mapX=" + mapX + ", mapY=" + mapY +
|
||||
", sideDistX=" + sideDistX.toString() +
|
||||
", sideDistY=" + sideDistY.toString());
|
||||
}
|
||||
if (map[mapY][mapX] > 0) {
|
||||
if (x == debugRay)
|
||||
console.log(" hit side 0");
|
||||
perpWallDist = (mapX - rayPosX + (1 - stepX) / 2) / rayDirX;
|
||||
wallX = (rayPosY + perpWallDist * rayDirY) % 1;
|
||||
if (rayDirX > 0)
|
||||
wallX = 1 - wallX;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sideDistY += deltaDistY;
|
||||
mapY += stepY;
|
||||
if (x == debugRay) {
|
||||
console.log(" side1, mapX=" + mapX + ", mapY=" + mapY +
|
||||
", sideDistX=" + sideDistX.toString() +
|
||||
", sideDistY=" + sideDistY.toString());
|
||||
}
|
||||
if (map[mapY][mapX] > 0) {
|
||||
if (x == debugRay)
|
||||
console.log(" hit side 1");
|
||||
perpWallDist = (mapY - rayPosY + (1 - stepY) / 2) / rayDirY;
|
||||
wallX = (rayPosX + perpWallDist * rayDirX) % 1;
|
||||
if (rayDirY < 0)
|
||||
wallX = 1 - wallX;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
perpWallDist = Math.abs(perpWallDist);
|
||||
|
||||
if (perpWallDist == 0)
|
||||
return { wallType:0, textureX:0, height:0 };
|
||||
|
||||
// Calculate height of line to draw on screen
|
||||
var lineHeight = Math.abs(Math.floor(screenHeight / perpWallDist));
|
||||
|
||||
if (x == debugRay) {
|
||||
console.debug(" perpWallDist=" + perpWallDist.toString() +
|
||||
", lineHeight=" + lineHeight.toString());
|
||||
}
|
||||
|
||||
// Wrap it all in a nice package.
|
||||
return { wallType:map[mapY][mapX], textureX:wallX, height:lineHeight };
|
||||
}
|
||||
|
||||
function drawStrip(stripIdx, lineData)
|
||||
{
|
||||
var strip = screenStrips[stripIdx];
|
||||
|
||||
// width is the same, but we have to stretch the texture to a factor of stripWidth to make it fill the strip correctly
|
||||
var width = lineData.height * stripWidth;
|
||||
|
||||
// top placement is easy since everything is centered on the x-axis, so we simply move
|
||||
// it half way down the screen and then half the wall height back up.
|
||||
var top = Math.round((screenHeight - lineData.height) / 2);
|
||||
|
||||
strip.style.height = lineData.height+"px";
|
||||
strip.style.top = top+"px";
|
||||
|
||||
strip.img.style.height = Math.floor(lineData.height * numTextures) + "px";
|
||||
strip.img.style.width = Math.floor(width*2) +"px";
|
||||
strip.img.style.top = -Math.floor(lineData.height * (lineData.wallType-1)) + "px";
|
||||
|
||||
var texX = Math.round(lineData.textureX*width);
|
||||
|
||||
if (texX > width - stripWidth)
|
||||
texX = width - stripWidth;
|
||||
|
||||
strip.img.style.left = -texX + "px";
|
||||
}
|
||||
|
||||
function move() {
|
||||
var moveStep = player.speed * player.moveSpeed; // player will move this far along the current direction vector
|
||||
|
||||
// add rotation if player is rotating (player.dir != 0)
|
||||
player.angleNum = player.angleNum + player.dir;
|
||||
if (player.angleNum < 0)
|
||||
player.angleNum += maxAngleNum;
|
||||
else if (player.angleNum >= maxAngleNum)
|
||||
player.angleNum -= maxAngleNum;
|
||||
|
||||
var newX = player.x + Math.cos(playerAngle()) * moveStep; // calculate new player position with simple trigonometry
|
||||
var newY = player.y + Math.sin(playerAngle()) * moveStep;
|
||||
|
||||
if (isBlocking(newX, newY)) { // are we allowed to move to the new position?
|
||||
return; // no, bail out.
|
||||
}
|
||||
|
||||
player.x = newX; // set new position
|
||||
player.y = newY;
|
||||
}
|
||||
|
||||
function isBlocking(x,y) {
|
||||
|
||||
// first make sure that we cannot move outside the boundaries of the level
|
||||
if (y < 0 || y >= mapHeight || x < 0 || x >= mapWidth)
|
||||
return true;
|
||||
|
||||
// return true if the map block is not 0, ie. if there is a blocking wall.
|
||||
return (map[Math.floor(y)][Math.floor(x)] != 0);
|
||||
}
|
||||
|
||||
function updateMiniMap() {
|
||||
|
||||
var miniMap = $("minimap");
|
||||
var miniMapObjects = $("minimapobjects");
|
||||
|
||||
var objectCtx = miniMapObjects.getContext("2d");
|
||||
miniMapObjects.width = miniMapObjects.width;
|
||||
|
||||
objectCtx.fillStyle = "red";
|
||||
objectCtx.fillRect( // draw a dot at the current player position
|
||||
player.x * miniMapScale - 2,
|
||||
player.y * miniMapScale - 2,
|
||||
4, 4
|
||||
);
|
||||
|
||||
objectCtx.strokeStyle = "red";
|
||||
objectCtx.beginPath();
|
||||
objectCtx.moveTo(player.x * miniMapScale, player.y * miniMapScale);
|
||||
objectCtx.lineTo(
|
||||
(player.x + Math.cos(playerAngle()) * 4) * miniMapScale,
|
||||
(player.y + Math.sin(playerAngle()) * 4) * miniMapScale
|
||||
);
|
||||
objectCtx.closePath();
|
||||
objectCtx.stroke();
|
||||
}
|
||||
|
||||
function drawMiniMap() {
|
||||
|
||||
// draw the topdown view minimap
|
||||
|
||||
var miniMap = $("minimap"); // the actual map
|
||||
var miniMapCtr = $("minimapcontainer"); // the container div element
|
||||
var miniMapObjects = $("minimapobjects"); // the canvas used for drawing the objects on the map (player character, etc)
|
||||
|
||||
miniMap.width = mapWidth * miniMapScale; // resize the internal canvas dimensions
|
||||
miniMap.height = mapHeight * miniMapScale; // of both the map canvas and the object canvas
|
||||
miniMapObjects.width = miniMap.width;
|
||||
miniMapObjects.height = miniMap.height;
|
||||
|
||||
var w = (mapWidth * miniMapScale) + "px" // minimap CSS dimensions
|
||||
var h = (mapHeight * miniMapScale) + "px"
|
||||
miniMap.style.width = miniMapObjects.style.width = miniMapCtr.style.width = w;
|
||||
miniMap.style.height = miniMapObjects.style.height = miniMapCtr.style.height = h;
|
||||
|
||||
var ctx = miniMap.getContext("2d");
|
||||
|
||||
ctx.fillStyle = "white";
|
||||
ctx.fillRect(0,0,miniMap.width,miniMap.height);
|
||||
|
||||
// loop through all blocks on the map
|
||||
for (var y=0;y<mapHeight;y++) {
|
||||
for (var x=0;x<mapWidth;x++) {
|
||||
|
||||
var wall = map[y][x];
|
||||
|
||||
if (wall > 0) { // if there is a wall block at this (x,y) ...
|
||||
|
||||
ctx.fillStyle = "rgb(200,200,200)";
|
||||
ctx.fillRect( // ... then draw a block on the minimap
|
||||
x * miniMapScale,
|
||||
y * miniMapScale,
|
||||
miniMapScale,miniMapScale
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateMiniMap();
|
||||
}
|
||||
|
||||
setTimeout(init, 1);
|
BIN
Platform/Apple/virtual/src/raycast/javascript/walls.png
Executable file
BIN
Platform/Apple/virtual/src/raycast/javascript/walls.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user