// http://lodev.org/cgtutor/raycasting.html
var raycast = raycast || {};
raycast.engine = (function () {
var canvas = document.getElementById("viewport");
var g = canvas.getContext("2d");
var filtering = false;
var mapWidth = 24,
mapHeight = 24,
texHeight = 64,
texWidth = 64;
var texture;
function initTexture() {
texture = raycast.texture.getTextures();
console.log(texture);
texture.push([]);
for(var x = 0; x < texWidth; x++) {
for(var y = 0; y < texHeight; y++) {
var xorcolor = (x * 256 / texWidth) ^ (y * 256 / texHeight);
var d = Math.sqrt((texWidth/2 - x)*(texWidth/2 - x) + (texHeight/2 - y)*(texHeight/2 - y));
var sincolor = 256 * (1 + Math.sin(d/2)) / 2;
texture[3][texWidth * y + x] = [xorcolor, 0, sincolor]; // blue sine pattern
}
}
}
/*
texture[0] = raycast.texture.load("ground");
texture[1] = raycast.texture.load("brick");
texture[2] = raycast.texture.load("sky");
*/
var worldMap = [
[1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,1,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,1,0,0,1,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1]
];
var posX = 2, posY = 2,
dirX = -1, dirY = 0,
planeX = 0, planeY = 0.66,
time = Date.now(), oldTime = Date.now();
var moveSpeed, rotSpeed;
var w = canvas.width,
h = canvas.height;
function verLine(arr, x, yStart, yEnd, color){
//console.log(x, yStart, yEnd);
for (var y = yStart | 0; y < yEnd | 0; y++) {
var i = 4 * (w * y) + 4 * x;
arr[i + 0] = color[0];
arr[i + 1] = color[1];
arr[i + 2] = color[2];
arr[i + 3] = 255;
}
};
imagedata = g.getImageData(0,0,w,h);
var buffer = imagedata.data;
var keys = raycast.keyhandler;
function input() {
if (keys.isKeydown("up")) {
if(worldMap[(posX + dirX * moveSpeed) | 0][posY | 0] == 0) posX += dirX * moveSpeed;
if(worldMap[posX | 0][(posY + dirY * moveSpeed) | 0] == 0) posY += dirY * moveSpeed;
}
//move backwards if no wall behind you
if (keys.isKeydown("down")) {
if(worldMap[(posX - dirX * moveSpeed) | 0][posY | 0] == 0) posX -= dirX * moveSpeed;
if(worldMap[posX | 0][(posY - dirY * moveSpeed) | 0] == 0) posY -= dirY * moveSpeed;
}
if (keys.isKeydown("right")) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * Math.cos(-rotSpeed) - dirY * Math.sin(-rotSpeed);
dirY = oldDirX * Math.sin(-rotSpeed) + dirY * Math.cos(-rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * Math.cos(-rotSpeed) - planeY * Math.sin(-rotSpeed);
planeY = oldPlaneX * Math.sin(-rotSpeed) + planeY * Math.cos(-rotSpeed);
}
if (keys.isKeydown("left")) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * Math.cos(rotSpeed) - dirY * Math.sin(rotSpeed);
dirY = oldDirX * Math.sin(rotSpeed) + dirY * Math.cos(rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * Math.cos(rotSpeed) - planeY * Math.sin(rotSpeed);
planeY = oldPlaneX * Math.sin(rotSpeed) + planeY * Math.cos(rotSpeed);
}
if (keys.isKeypress("d"))
filtering = !filtering;
}
function draw() {
for(var x = 0; x < w; x++) {
var cameraX = 2 * x / w - 1,
rayPosX = posX,
rayPosY = posY,
rayDirX = dirX + planeX * cameraX,
rayDirY = dirY + planeY * cameraX;
var mapX = rayPosX | 0,
mapY = rayPosY | 0;
var deltaDistX = Math.sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX)),
deltaDistY = Math.sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
var stepX,
stepY,
sideDistX,
sideDistY;
if (rayDirX < 0) {
stepX = -1;
sideDistX = (rayPosX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (rayPosY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY;
}
var side, hit = 0;
// DDA
while (hit == 0) {
side = sideDistX > sideDistY;
if (side == 0) {
sideDistX += deltaDistX;
mapX += stepX;
} else {
sideDistY += deltaDistY;
mapY += stepY;
}
if (worldMap[mapX][mapY] > 0) {
hit = 1;
}
}
var perpWallDist;
if (side == 0)
perpWallDist = Math.abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX);
else
perpWallDist = Math.abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY);
//Calculate height of line to draw on screen
var lineHeight = Math.abs((h / perpWallDist) | 0);
//calculate lowest and highest pixel to fill in current stripe
var drawStart = ((h - lineHeight) / 2) | 0;
if(drawStart < 0)
drawStart = 0;
var drawEnd = ((h + lineHeight) / 2) | 0;
if(drawEnd >= h)
drawEnd = h - 1;
var wallX; // the exact value where the wall was hit
if (side == 1)
wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX;
else
wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY;
wallX -= wallX | 0;
var texX = (wallX * texWidth)/* | 0*/;
if(side == 0 && rayDirX > 0)
texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0)
texX = texWidth - texX - 1;
var shade = (side == 1 ? 0.6: 1);
var wallTex = texture[worldMap[mapX][mapY] - 1];
for (var y = drawStart; y < drawEnd; y++) {
var d = (y * 256 - h * 128 + lineHeight * 128) | 0;
var texY = ((d * texHeight) / (lineHeight * 256))/* | 0*/;
if (texY < 0) texY = 0;
var color;
if (filtering) {
var ty1 = texY | 0;
var ty2 = (ty1 + 1) % texHeight;
var tx1 = texX | 0;
var tx2 = (tx1 + 1) % texWidth;
var xf = texX - (texX | 0);
var yf = texY - (texY | 0);
color = [0,0,0];
var c1 = wallTex[texWidth * ty1 + tx1];
var c2 = wallTex[texWidth * ty1 + tx2];
var c3 = wallTex[texWidth * ty2 + tx1];
var c4 = wallTex[texWidth * ty2 + tx2];
color[0] = (c1[0]*(1-xf)*(1-yf) + c2[0]*xf*(1-yf) + c3[0]*(1-xf)*yf + c4[0]*xf*yf) | 0;
color[1] = (c1[1]*(1-xf)*(1-yf) + c2[1]*xf*(1-yf) + c3[1]*(1-xf)*yf + c4[1]*xf*yf) | 0;
color[2] = (c1[2]*(1-xf)*(1-yf) + c2[2]*xf*(1-yf) + c3[2]*(1-xf)*yf + c4[2]*xf*yf) | 0;
} else {
texX |= 0;
texY |= 0;
color = wallTex[texHeight * texY + texX];
}
var i = 4 * (w * y) + 4 * x;
buffer[i + 0] = color[0] * shade;
buffer[i + 1] = color[1] * shade;
buffer[i + 2] = color[2] * shade;
buffer[i + 3] = 255;
}
var floorXWall, floorYWall; //x, y position of the floor texel at the bottom of the wall
//4 different wall directions possible
if (side == 0 && rayDirX > 0) {
floorXWall = mapX;
floorYWall = mapY + wallX;
} else if (side == 0 && rayDirX < 0) {
floorXWall = mapX + 1.0;
floorYWall = mapY + wallX;
} else if (side == 1 && rayDirY > 0) {
floorXWall = mapX + wallX;
floorYWall = mapY;
} else /* side == 1 && rayDirY > 0*/{
floorXWall = mapX + wallX;
floorYWall = mapY + 1.0;
}
var currentDist;
var distWall = perpWallDist;
var distPlayer = 0.0;
if (drawEnd < 0) drawEnd = h; //becomes < 0 when the integer overflows
var ceilTex = texture[2];
var floorTex = texture[1];
//draw the floor from drawEnd to the bottom of the screen
for(var y = drawEnd; y < h; y++)
{
currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
var weight = (currentDist - distPlayer) / (distWall - distPlayer);
var nextWeight = (h / (2.0 * (y+1) - h)) / distWall;
var currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
var currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
var floorTexX = (currentFloorX * texWidth) % texWidth;
var floorTexY = (currentFloorY * texHeight) % texHeight;
if (floorTexX < 0) floorTexX = 0;
if (floorTexY < 0) floorTexY = 0;
var color;
if (filtering) {
ty1 = floorTexY | 0;
ty2 = (ty1 + 1) % texHeight;
tx1 = floorTexX | 0;
tx2 = (tx1 + 1) % texWidth;
xf = floorTexX - (floorTexX | 0);
yf = floorTexY - (floorTexY | 0);
color = [0,0,0];
c1 = floorTex[texWidth * ty1 + tx1];
c2 = floorTex[texWidth * ty1 + tx2];
c3 = floorTex[texWidth * ty2 + tx1];
c4 = floorTex[texWidth * ty2 + tx2];
color[0] = (c1[0]*(1-xf)*(1-yf) + c2[0]*xf*(1-yf) + c3[0]*(1-xf)*yf + c4[0]*xf*yf) | 0;
color[1] = (c1[1]*(1-xf)*(1-yf) + c2[1]*xf*(1-yf) + c3[1]*(1-xf)*yf + c4[1]*xf*yf) | 0;
color[2] = (c1[2]*(1-xf)*(1-yf) + c2[2]*xf*(1-yf) + c3[2]*(1-xf)*yf + c4[2]*xf*yf) | 0;
} else {
color = floorTex[texWidth * (floorTexY|0) + (floorTexX|0)];
}
//floor
i = 4 * (w * y) + 4 * x;
buffer[i+0] = (color[0])/2;
buffer[i+1] = (color[1])/2;
buffer[i+2] = (color[2])/2;
buffer[i+3] = 255;
if (filtering) {
color = [0,0,0];
c1 = ceilTex[texWidth * ty1 + tx1];
c2 = ceilTex[texWidth * ty1 + tx2];
c3 = ceilTex[texWidth * ty2 + tx1];
c4 = ceilTex[texWidth * ty2 + tx2];
color[0] = (c1[0]*(1-xf)*(1-yf) + c2[0]*xf*(1-yf) + c3[0]*(1-xf)*yf + c4[0]*xf*yf) | 0;
color[1] = (c1[1]*(1-xf)*(1-yf) + c2[1]*xf*(1-yf) + c3[1]*(1-xf)*yf + c4[1]*xf*yf) | 0;
color[2] = (c1[2]*(1-xf)*(1-yf) + c2[2]*xf*(1-yf) + c3[2]*(1-xf)*yf + c4[2]*xf*yf) | 0;
} else {
color = ceilTex[texWidth * (floorTexY|0) + (floorTexX|0)];
}
//ceiling (symmetrical!)
i = 4 * (w * (h - y - 1)) + 4 * x;
buffer[i+0] = color[0]/2;
buffer[i+1] = color[1]/2;
buffer[i+2] = color[2]/2;
buffer[i+3] = 255;
}
}
oldTime = time;
time = Date.now()
var frameTime = (time - oldTime) / 1000.0; //frameTime is the time this frame has taken, in seconds
//speed modifiers
moveSpeed = frameTime * 5.0; //the constant value is in squares/second
rotSpeed = frameTime * 3.0; //the constant value is in radians/second
g.putImageData(imagedata, 0, 0);
g.font = "bold 30pt Monospace";
g.fillText(""+((1000 / (time - oldTime))|0), 0, 30);
};
function tick() {
draw();
input();
keys.tick();
window.requestAnimFrame(tick);
//window.setTimeout(tick, 1);
};
function start() {
initTexture();
tick();
}
return {start: start}
}());