asdqwde
This commit is contained in:
@@ -171,7 +171,6 @@ class DungeonCrawler {
|
|||||||
textureUrls.forEach((url, textureId) => {
|
textureUrls.forEach((url, textureId) => {
|
||||||
Texture.fromSource(url).then((texture) => {
|
Texture.fromSource(url).then((texture) => {
|
||||||
textures[textureId] = texture;
|
textures[textureId] = texture;
|
||||||
console.log("here", { textureId, texture, textures });
|
|
||||||
textureLoadCount++;
|
textureLoadCount++;
|
||||||
|
|
||||||
if (textureLoadCount < textureUrls.length) {
|
if (textureLoadCount < textureUrls.length) {
|
||||||
|
|||||||
@@ -142,23 +142,16 @@ export class FirstPersonRenderer {
|
|||||||
const screenWidth = this.window.width;
|
const screenWidth = this.window.width;
|
||||||
|
|
||||||
/** @type {Map<number,Tile} The coordinates of all the tiles checked while rendering this frame*/
|
/** @type {Map<number,Tile} The coordinates of all the tiles checked while rendering this frame*/
|
||||||
const coordsCheckedFrame = new Map();
|
const coordsChecked = new Map();
|
||||||
|
|
||||||
for (let x = 0; x < screenWidth; x++) {
|
for (let x = 0; x < screenWidth; x++) {
|
||||||
/** @type {Map<number,Tile} The coordinates of all the tiles checked while casting this single ray*/
|
|
||||||
const coordsCheckedRay = new Map();
|
|
||||||
|
|
||||||
const angleOffset = (x / screenWidth - 0.5) * this.fov; // in radians
|
const angleOffset = (x / screenWidth - 0.5) * this.fov; // in radians
|
||||||
const rayAngle = dirAngle + angleOffset;
|
const rayAngle = dirAngle + angleOffset;
|
||||||
const rayDirX = Math.cos(rayAngle);
|
const rayDirX = Math.cos(rayAngle);
|
||||||
const rayDirY = Math.sin(rayAngle);
|
const rayDirY = Math.sin(rayAngle);
|
||||||
|
|
||||||
// Cast ray using our DDA function
|
// Cast ray using our DDA function
|
||||||
const ray = this.castRay(posX, posY, rayDirX, rayDirY, coordsCheckedRay);
|
const ray = this.castRay(posX, posY, rayDirX, rayDirY, coordsChecked);
|
||||||
|
|
||||||
coordsCheckedRay.forEach((tile, idx) => {
|
|
||||||
coordsCheckedFrame.set(idx, tile);
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render a single screen column
|
// Render a single screen column
|
||||||
@@ -195,83 +188,97 @@ export class FirstPersonRenderer {
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
renderColumn(x, ray, rayDirX, rayDirY, angleOffset) {
|
renderColumn(x, ray, rayDirX, rayDirY, angleOffset) {
|
||||||
|
// //
|
||||||
|
// // Check if we hit anything at all
|
||||||
|
// if (ray.collisions.length === 0) {
|
||||||
|
// //
|
||||||
|
// // We didn't hit anything. Just paint floor, wall, and darkness
|
||||||
|
// for (let y = 0; y < this.window.height; y++) {
|
||||||
|
// const [char, color] = this.shades[y];
|
||||||
|
// this.window.put(x, y, char, color);
|
||||||
|
// }
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// Check if we hit anything at all
|
// // ALTERNATIVE always paint floor and ceiling
|
||||||
if (ray.collisions.length === 0) {
|
for (let y = 0; y < this.window.height; y++) {
|
||||||
//
|
const [char, color] = this.shades[y];
|
||||||
// We didn't hit anything. Just paint floor, wall, and darkness
|
this.window.put(x, y, char, color);
|
||||||
for (let y = 0; y < this.window.height; y++) {
|
|
||||||
const [char, color] = this.shades[y];
|
|
||||||
this.window.put(x, y, char, color);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rayLength, side, sampleU, tile: wallTile } = ray.collisions[0];
|
for (const { rayLength, side, sampleU, tile } of ray.collisions) {
|
||||||
|
let distance = Math.max(rayLength * Math.cos(angleOffset), 1e-12); // Avoid divide by zero
|
||||||
|
|
||||||
const distance = Math.max(rayLength * Math.cos(angleOffset), 1e-12); // Avoid divide by zero
|
|
||||||
|
|
||||||
//
|
|
||||||
// Calculate perspective.
|
|
||||||
//
|
|
||||||
const screenHeight = this.window.height;
|
|
||||||
const lineHeight = Math.round(screenHeight / distance); // using round() because floor() gives aberrations when distance == (n + 0.500)
|
|
||||||
const halfScreenHeight = screenHeight / 2;
|
|
||||||
const halfLineHeight = lineHeight / 2;
|
|
||||||
|
|
||||||
let minY = Math.floor(halfScreenHeight - halfLineHeight);
|
|
||||||
let maxY = Math.floor(halfScreenHeight + halfLineHeight);
|
|
||||||
let unsafeMinY = minY; // can be lower than zero - it happens when we get so close to a wall we cannot see top or bottom
|
|
||||||
|
|
||||||
if (minY < 0) {
|
|
||||||
minY = 0;
|
|
||||||
}
|
|
||||||
if (maxY >= screenHeight) {
|
|
||||||
maxY = screenHeight - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Pick texture (here grid value decides which texture)
|
|
||||||
//
|
|
||||||
const wallTexture = this.textures[wallTile.textureId];
|
|
||||||
|
|
||||||
for (let y = 0; y < screenHeight; y++) {
|
|
||||||
//
|
//
|
||||||
// Are we hitting the ceiling?
|
// Calculate perspective.
|
||||||
//
|
//
|
||||||
if (y < minY || y > maxY) {
|
const screenHeight = this.window.height;
|
||||||
const [char, color] = this.shades[y];
|
const lineHeight = Math.round(screenHeight / distance); // using round() because floor() gives aberrations when distance == (n + 0.500)
|
||||||
this.window.put(x, y, char, color);
|
const halfScreenHeight = screenHeight / 2;
|
||||||
continue;
|
const halfLineHeight = lineHeight / 2;
|
||||||
|
|
||||||
|
let minY = Math.floor(halfScreenHeight - halfLineHeight);
|
||||||
|
let maxY = Math.floor(halfScreenHeight + halfLineHeight);
|
||||||
|
let unsafeMinY = minY; // can be lower than zero - it happens when we get so close to a wall we cannot see top or bottom
|
||||||
|
|
||||||
|
if (minY < 0) {
|
||||||
|
minY = 0;
|
||||||
}
|
}
|
||||||
if (y === minY) {
|
if (maxY >= screenHeight) {
|
||||||
this.window.put(x, y, "m", "#0F0");
|
maxY = screenHeight - 1;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (y === maxY) {
|
|
||||||
this.window.put(x, y, "M", "#F00");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Map screen y to texture y
|
// Pick texture (here grid value decides which texture)
|
||||||
let sampleV = (y - unsafeMinY) / lineHeight; // y- coordinate of the texture point to sample
|
|
||||||
|
|
||||||
const color = wallTexture.sample(sampleU, sampleV);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// North-south walls are shaded differently from east-west walls
|
const texture = this.textures[tile.textureId];
|
||||||
let shade = side === Side.X_AXIS ? 0.8 : 1.0; // MAGIC NUMBERS
|
|
||||||
|
|
||||||
//
|
for (let y = 0; y < screenHeight; y++) {
|
||||||
// Dim walls that are far away
|
//
|
||||||
const lightLevel = 1 - rayLength / this.viewDistance;
|
// Are we hitting the ceiling?
|
||||||
|
//
|
||||||
|
if (y < minY || y > maxY) {
|
||||||
|
const [char, color] = this.shades[y];
|
||||||
|
this.window.put(x, y, char, color);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// // DEBUG LINES
|
||||||
|
// if (y === minY) {
|
||||||
|
// this.window.put(x, y, "m", "#0F0");
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// if (y === maxY) {
|
||||||
|
// this.window.put(x, y, "M", "#F00");
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
//
|
//
|
||||||
// Darken the image
|
// Map screen y to texture y
|
||||||
color.mulRGB(shade * lightLevel);
|
let sampleV = (y - unsafeMinY) / lineHeight; // y- coordinate of the texture point to sample
|
||||||
|
|
||||||
this.window.put(x, y, this.wallChar, color.toCSS());
|
const color = texture.sample(sampleU, sampleV);
|
||||||
|
if (!Number.isFinite(color.a)) {
|
||||||
|
throw new Error("Waaat");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.a === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// North-south walls are shaded differently from east-west walls
|
||||||
|
let shade = side === Side.X_AXIS ? 0.8 : 1.0; // MAGIC NUMBERS
|
||||||
|
|
||||||
|
//
|
||||||
|
// Dim walls that are far away
|
||||||
|
const lightLevel = 1 - rayLength / this.viewDistance;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Darken the image
|
||||||
|
color.mulRGB(shade * lightLevel);
|
||||||
|
|
||||||
|
this.window.put(x, y, tile.sprite ? "#" : this.wallChar, color.toCSS()); // MAGIC CONSTANT "S"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,35 +389,37 @@ export class FirstPersonRenderer {
|
|||||||
const tile = this.map.get(mapX, mapY);
|
const tile = this.map.get(mapX, mapY);
|
||||||
coordsChecked.set(this.map.tileIdx(mapX, mapY), tile);
|
coordsChecked.set(this.map.tileIdx(mapX, mapY), tile);
|
||||||
|
|
||||||
|
//
|
||||||
|
// --------------------------
|
||||||
|
// No collision? Move on
|
||||||
|
// --------------------------
|
||||||
|
if (!tile.collision) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const rayLength = Math.hypot(
|
const rayLength = Math.hypot(
|
||||||
wallDist * dirX, //
|
wallDist * dirX, //
|
||||||
wallDist * dirY, //
|
wallDist * dirY, //
|
||||||
);
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
// --------------------------
|
// Prepend the element to the array so rear-most sprites
|
||||||
// Add a Sprite to the result
|
// appear first in the array,
|
||||||
// --------------------------
|
// enabling us to simply draw from back to front
|
||||||
if (tile.sprite || tile.wall) {
|
const collision = new RayCollision();
|
||||||
//
|
collision.mapX = mapX;
|
||||||
// Prepend the element to the array so rear-most sprites
|
collision.mapY = mapY;
|
||||||
// appear first in the array,
|
collision.rayLength = rayLength;
|
||||||
// enabling us to simply draw from back to front
|
collision.tile = tile;
|
||||||
const collision = new RayCollision();
|
collision.sampleU = sampleU;
|
||||||
collision.mapX = mapX;
|
collision.side = side;
|
||||||
collision.mapY = mapY;
|
result.collisions.unshift(collision);
|
||||||
collision.rayLength = rayLength;
|
|
||||||
collision.tile = tile;
|
|
||||||
collision.sampleU = sampleU;
|
|
||||||
collision.side = side;
|
|
||||||
result.collisions.unshift(collision);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// --------------------------
|
// --------------------------------
|
||||||
// Add a Wall to the result
|
// Algorithm stops if the ray hits
|
||||||
// (and return)
|
// a wall.
|
||||||
// --------------------------
|
// -------------------------------
|
||||||
if (tile.wall) {
|
if (tile.wall) {
|
||||||
result.hitWall = true;
|
result.hitWall = true;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ export class Texture {
|
|||||||
* @returns {NRGBA}
|
* @returns {NRGBA}
|
||||||
*/
|
*/
|
||||||
sample(u, v) {
|
sample(u, v) {
|
||||||
const x = Math.round(u * this.width);
|
const x = Math.min(this.width - 1, Math.round(u * this.width));
|
||||||
const y = Math.round(v * this.height);
|
const y = Math.min(this.height - 1, Math.round(v * this.height));
|
||||||
const index = (y * this.width + x) * 4;
|
const index = (y * this.width + x) * 4;
|
||||||
return new NRGBA(
|
return new NRGBA(
|
||||||
this.data[index + 0] / 255,
|
this.data[index + 0] / 255,
|
||||||
this.data[index + 1] / 255,
|
this.data[index + 1] / 255,
|
||||||
this.data[index + 2] / 255,
|
this.data[index + 2] / 255,
|
||||||
1, // this.data[index + 3] / 255,
|
this.data[index + 3] / 255,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class Tile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCollision() {
|
get collision() {
|
||||||
return this.wall || this.sprite;
|
return this.wall || this.sprite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,7 @@ export const defaultLegend = Object.freeze({
|
|||||||
minimapColor: "#f00",
|
minimapColor: "#f00",
|
||||||
traversable: false,
|
traversable: false,
|
||||||
wall: false,
|
wall: false,
|
||||||
|
sprite: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
BIN
frontend/gnoll.png
Executable file → Normal file
BIN
frontend/gnoll.png
Executable file → Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 8.2 KiB |
Reference in New Issue
Block a user