This repository has been archived on 2023-07-08. You can view files and clone it, but cannot push or open issues or pull requests.
awesome/proj/wolfystein.html

280 lines
No EOL
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>Wolfystein</title>
<meta charset="UTF-8">
</head>
<body style="background: black;">
<img src="skybox.png" id="skybox" style="display: none;">
<canvas id="c" style="position: absolute;"></canvas>
<p id="debug" style="color: white;"></p> <!-- a debug/cheat CONSOLE would be really cool -->
<script>
// screen constants
const resx = 100;
const resy = 60;
const pixSize = 6;
const focal = 1;
const rayiter = 100;
// world
var map = [];
const tileColors = [
[194, 228, 255], // sky
[250, 250, 250],
[250, 140, 170]
];
for (let z = 0; z < 32; z++) {
map.push([]);
for (let y = 0; y < 32; y++) {
map[z].push([]);
for (let x = 0; x < 32; x++) {
map[z][y].push(Math.random() * 3 >> 0);
}
}
}
// skybox
{
let img = document.querySelector('#skybox');
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0);
// TODO use -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray
const skybox = context.getImageData(0, 0, img.width-1, img.height-1).data;
}
// player data
var px = -2;
var py = -2;
var pz = 2;
var ppitch = -0.3;
var pyaw = -0.7;
const pspeed = 7.5;
const pturnspeed = 0.01;
// input
var left = false;
var right = false;
var up = false;
var down = false;
var space = false;
var shift = false;
var mdx = 0;
var mdy = 0;
var focused = false;
// initialize debug
var debug = document.getElementById('debug');
// initialize screen
var c = document.getElementById('c');
var ctx = c.getContext('2d');
c.width = resx * pixSize;
c.height = resy * pixSize;
ctx.clearRect(0, 0, resx * pixSize, resy * pixSize);
document.addEventListener('keydown', function(event) {
if (event.keyCode == 65) { left = true; }
if (event.keyCode == 68) { right = true; }
if (event.keyCode == 87) { up = true; }
if (event.keyCode == 83) { down = true; }
if (event.keyCode == 32) { space = true; }
if (event.keyCode == 16) { shift = true; }
}, true);
document.addEventListener('keyup', function(event) {
if (event.keyCode == 65) { left = false; }
if (event.keyCode == 68) { right = false; }
if (event.keyCode == 87) { up = false; }
if (event.keyCode == 83) { down = false; }
if (event.keyCode == 32) { space = false; }
if (event.keyCode == 16) { shift = false; }
}, true);
c.addEventListener("click", async () => { // https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API
await c.requestPointerLock({
unadjustedMovement: true,
});
});
c.addEventListener('mousemove', function(event) {
mdx += event.movementX;
mdy += event.movementY;
}, true);
document.addEventListener("pointerlockchange", function(event) {
focused = document.pointerLockElement === c;
}, false);
var prevTimestamp = 0;
async function update(timestamp) {
// get important info
let dt = (timestamp - prevTimestamp);
prevTimestamp = timestamp;
debug.innerHTML =
"dt: " + dt + "ms<br>px: " + ((px * 100 >> 0) / 100) +
"u<br>py: " + ((py * 100 >> 0) / 100) +
"u<br>pz: " + ((pz * 100 >> 0) / 100) +
"u<br>ppitch: " + ((ppitch * 100 >> 0) / 100) +
"rad<br>pyaw: " + ((pyaw * 100 >> 0) / 100) +
"rad";
// if (dt < 50) { // fps limiter
// await new Promise(r => setTimeout(r, 50 - dt));
// dt = 50;
// }
dt /= 1000;
// center canvas
c.style.left = (window.innerWidth - c.width) / 2 + "px";
c.style.top = (window.innerHeight - c.height) / 2 + "px";
// input
if (focused) {
if (left) { px -= Math.sin(pyaw) * pspeed * dt; py -= Math.cos(pyaw) * pspeed * dt; }
if (right) { px += Math.sin(pyaw) * pspeed * dt; py += Math.cos(pyaw) * pspeed * dt; }
if (up) { px += Math.cos(pyaw) * pspeed * dt; py -= Math.sin(pyaw) * pspeed * dt; }
if (down) { px -= Math.cos(pyaw) * pspeed * dt; py += Math.sin(pyaw) * pspeed * dt; }
if (space) { pz += pspeed * dt; }
if (shift) { pz -= pspeed * dt; }
ppitch -= mdy * pturnspeed;
ppitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, ppitch));
pyaw -= mdx * pturnspeed;
pyaw = (pyaw + Math.PI * 2) % (Math.PI * 2);
mdx = 0;
mdy = 0;
}
// draw
for (let x=resx;x--;) {
let h = Math.atan((x / resx - 0.5) / focal);
let sh = Math.sin(h);
let ch = Math.cos(h);
let syaw = Math.sin(-pyaw);
let cyaw = Math.cos(-pyaw);
for (let y=resy;y--;) {
let raydata = raycast(
px, py, pz,
ppitch - Math.atan((y / resy - 0.5) / focal * resy / resx),
sh, ch, syaw, cyaw
);
if (raydata.type == 0) {
// skybox (have to get UV coords...)
ctx.fillStyle =
'rgba(' +
(raydata.dx * 127 + 127)
+ ',' +
(raydata.dy * 127 + 127)
+ ',' +
(raydata.dz * 127 + 127)
+ ',1)';
} else {
// map
let shadow = Math.pow(1 / (raydata.dist + 1), 0.7) / 2 + 0.5;
if (raydata.x % 1 > 0.99) {
shadow *= 1.15;
} else if (raydata.y % 1 > 0.99) {
shadow *= 1.25;
} else if (raydata.z % 1 > 0.99) {
shadow *= 1.5;
}
ctx.fillStyle =
'rgba(' +
(tileColors[raydata.type][0] * shadow)
+ ',' +
(tileColors[raydata.type][1] * shadow)
+ ',' +
(tileColors[raydata.type][2] * shadow)
+ ',1)';
}
ctx.fillRect(x * pixSize, y * pixSize, pixSize, pixSize);
}
}
// loop
requestAnimationFrame(update);
}
// sends a ray from the given point in the given direction, returns data based on the raycast
// https://math.stackexchange.com/questions/2645689/what-is-the-parametric-equation-of-a-rotated-ellipse-given-the-angle-of-rotatio
function raycast(x, y, z, pitch, sh, ch, syaw, cyaw) {
let dist = 0.0;
let r = Math.cos(pitch);
let dx = (r * ch * cyaw - sh * syaw);
let dy = (r * ch * syaw + sh * cyaw);
let dz = Math.sqrt(1 - dx * dx - dy * dy) * Math.sign(pitch);
// cast a ray into the map
let signX = Math.sign(dx);
let signY = Math.sign(dy);
let signZ = Math.sign(dz);
for (let i=rayiter;i--;) {
if (!(x < 0 || y < 0 || z < 0 || (x >> 0) >= map[0][0].length || (y >> 0) >= map[0].length || (z >> 0) >= map.length)) {
let type = map[z >> 0][y >> 0][x >> 0];
if (type != 0) {
return {
dist: dist,
type: type,
x: x,
y: y,
z: z
};
}
}
// find smallest f / d (ray length), use for step
let step =
Math.min(
Math.abs(((signX == 1 ? Math.floor(x+1) : Math.ceil(x-1)) - x) / dx),
Math.min(
Math.abs(((signY == 1 ? Math.floor(y+1) : Math.ceil(y-1)) - y) / dy),
Math.abs(((signZ == 1 ? Math.floor(z+1) : Math.ceil(z-1)) - z) / dz)
)) * 1.01; // go slightly more inwards instead of on the edge
x += dx * step;
y += dy * step;
z += dz * step;
dist += step;
}
// didn't hit anything
return {
dist: 0,
type: 0,
dx: dx, // for skybox UV mapping
dy: dy,
dz: dz
};
}
requestAnimationFrame(update);
</script>
</body>
</html>