From 08b85b38e02f776395624105f960fb9d46d76b66 Mon Sep 17 00:00:00 2001 From: rachitkakkar Date: Tue, 21 May 2024 11:51:32 -0500 Subject: [PATCH] Attempted sprite casting --- README.md | 2 +- raycaster.js | 102 ++++++++++++++++++++++++++++++++++++------ src/Player.js | 4 +- src/Sprite.js | 8 ++++ textures/barrel 2.png | Bin 0 -> 1132 bytes textures/barrel.png | Bin 0 -> 2324 bytes 6 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 src/Sprite.js create mode 100755 textures/barrel 2.png create mode 100644 textures/barrel.png diff --git a/README.md b/README.md index e46d593..b2dbcf5 100755 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - Features textures, a mini-map, procedural/maze generation, doors, fully implemented first person controller (including strafe and up/down mouselook), and more! - Uses modern javascript and browser features such as classes, modules, JSDoc (mostly), PointerLock API, etc. -*This project started as a quest to improve a much older raycaster I made a long time ago, and the original code plus its README can be found in the `Version 1` directory.* + # Credits Much of the work here is inspired by Lode Vandevenne's excellent tutorials on raycasting. diff --git a/raycaster.js b/raycaster.js index 24e5c7a..e24b6c8 100755 --- a/raycaster.js +++ b/raycaster.js @@ -6,6 +6,7 @@ import { Timer } from "./src/Timer.js"; import { MiniMap } from "./src/MiniMap.js"; import { OptionsHandler } from "./src/OptionsHandler.js"; import { Player } from "./src/Player.js"; +import { Sprite } from "./src/Sprite.js"; import * as Utils from "./src/Utils.js"; @@ -45,6 +46,13 @@ var playerSize = Utils.castToInt(padding * 4/5); var miniMap = new MiniMap(blockSize, playerSize, padding); +// Sprite setup +var sprites = [new Sprite(new Utils.Vector2(2, 1.5), barrelTexture)]; +var zBuffer = [] +for (let z = 0; z < screenWidth; z++) { + zBuffer.push(0); +} + // Main loop function main() { // Calculate delta time @@ -254,12 +262,12 @@ function main() { let dimFactor = 0.9 + (0.2 * (currentDistance)); let fogPercentage = (optionsHandler.getOption("fog")) ? 0.1 * currentDistance : 0; - let pixelindex = (ceilingTex.y * textureWidth + ceilingTex.x) * 4; - red = ceilingTexture.data[pixelindex] / dimFactor; + let pixelIndex = (ceilingTex.y * textureWidth + ceilingTex.x) * 4; + red = ceilingTexture.data[pixelIndex] / dimFactor; red = red * (1 - fogPercentage) + fogPercentage * 0.1; - green = ceilingTexture.data[pixelindex+1] / dimFactor; + green = ceilingTexture.data[pixelIndex+1] / dimFactor; green = green * (1 - fogPercentage) + fogPercentage * 0.1; - blue = ceilingTexture.data[pixelindex+2] / dimFactor; + blue = ceilingTexture.data[pixelIndex+2] / dimFactor; blue = blue * (1 - fogPercentage) + fogPercentage * 0.1; } @@ -276,26 +284,88 @@ function main() { let weight = currentDistance / perpendicularWallDistance; - let currentFloor = new Utils.Vector2(weight * floorCeilingWallPos.x + (1.0 - weight) * player.position.x, - weight * floorCeilingWallPos.y + (1.0 - weight) * player.position.y) + let currentFloor = new Utils.Vector2( + weight * floorCeilingWallPos.x + (1.0 - weight) * player.position.x, + weight * floorCeilingWallPos.y + (1.0 - weight) * player.position.y + ); - let floorTex = new Utils.Vector2(Utils.castToInt(currentFloor.x * textureWidth) % textureWidth, - Utils.castToInt(currentFloor.y * textureHeight) % textureHeight); + let floorTex = new Utils.Vector2( + Utils.castToInt(currentFloor.x * textureWidth) % textureWidth, + Utils.castToInt(currentFloor.y * textureHeight) % textureHeight + ); let dimFactor = 0.9 + (0.2 * (currentDistance)); let fogPercentage = (optionsHandler.getOption("fog")) ? 0.1 * currentDistance : 0; - let pixelindex = (floorTex.y * textureWidth + floorTex.x) * 4; - red = groundTexture.data[pixelindex] / dimFactor; + let pixelIndex = (floorTex.y * textureWidth + floorTex.x) * 4; + red = groundTexture.data[pixelIndex] / dimFactor; red = red * (1 - fogPercentage) + fogPercentage * 0.1; - green = groundTexture.data[pixelindex+1] / dimFactor; + green = groundTexture.data[pixelIndex+1] / dimFactor; green = green * (1 - fogPercentage) + fogPercentage * 0.1; - blue = groundTexture.data[pixelindex+2] / dimFactor; + blue = groundTexture.data[pixelIndex+2] / dimFactor; blue = blue * (1 - fogPercentage) + fogPercentage * 0.1; } renderer.drawPixel(x, y, red, green, blue); } + + // Set zBuffer for sprite casting + zBuffer[x] = perpendicularWallDistance; + for (const sprite of sprites) { + sprite.distance = ((player.position.x - sprite.position.x) * (player.position.x - sprite.position.x) + (player.position.y - sprite.position.y) * (player.position.y - sprite.position.y)); + } + sprites.sort((a, b) => a.distance - b.distance); + } + + for (const sprite of sprites) { + let relSpritePosition = new Utils.Vector2( + sprite.position.x - player.position.x, + sprite.position.y - player.position.y + ); + + let invDet = 1.0 / (player.plane.x * player.direction.y - player.direction.x * player.plane.y); + let transformPosition = new Utils.Vector2( + invDet * (player.direction.y * relSpritePosition.x - player.direction.x * relSpritePosition.y), + invDet * (-player.plane.y * relSpritePosition.x + player.plane.x * relSpritePosition.y) + ); + + let spriteScreenX = Utils.castToInt((screenWidth / 2) * (1 + transformPosition.x / transformPosition.y)); + + let spriteHeight = Math.abs(Utils.castToInt(screenHeight / (transformPosition.y))); + let drawStartY = -spriteHeight / 2 + screenHeight / 2; + if (drawStartY < 0) drawStartY = 0; + let drawEndY = spriteHeight / 2 + screenHeight / 2; + if (drawEndY >= screenHeight) drawEndY = screenHeight - 1; + + let spriteWidth = Math.abs(Utils.castToInt(screenHeight / (transformPosition.y))); + let drawStartX = -spriteWidth / 2 + spriteScreenX; + if (drawStartX < 0) drawStartX = 0; + let drawEndX = spriteWidth / 2 + spriteScreenX; + if (drawEndX >= screenWidth) drawEndX = screenWidth - 1; + + for (let stripe = drawStartX; stripe < drawEndX; stripe++) { + let texX = Utils.castToInt((stripe - (-spriteWidth / 2 + spriteScreenX)) * textureWidth / spriteWidth); + // the conditions in the if are: + // 1) it's in front of camera plane so you don't see things behind you + // 2) it's on the screen (left) + // 3) it's on the screen (right) + // 4) ZBuffer, with perpendicular distance + if (transformPosition.y > 0 && stripe > 0 && stripe < screenWidth && transformPosition.y < zBuffer[stripe]) { + for (let y = drawStartY; y < drawEndY; y++) { + let d = (y) - screenHeight + spriteHeight; + let texY = ((d * textureHeight) / spriteHeight); + let pixelIndex = (textureWidth * texY + texX) * 4; + let red = barrelTexture.data[pixelIndex] / 1; + // red = red * (1 - fogPercentage) + fogPercentage * 0.1; + let green = barrelTexture.data[pixelIndex+1] / 1; + // green = green * (1 - fogPercentage) + fogPercentage * 0.1; + let blue = barrelTexture.data[pixelIndex+2] / 1; + // blue = blue * (1 - fogPercentage) + fogPercentage * 0.1; + if (Utils.castToInt(red) != 0 && Utils.castToInt(green) != 0 && Utils.castToInt(blue) != 0) // Transparency for black + renderer.drawPixel(stripe, y, red, green, blue); + } + } + } } // Render minimap @@ -319,12 +389,13 @@ function main() { // Textures const textureWidth = 64; const textureHeight = 64; -const textureUrls = ["textures/bricks.png", "textures/tiles.png", "textures/tiles.png", "textures/door.png"]; +const textureUrls = ["textures/bricks.png", "textures/tiles.png", "textures/tiles.png", "textures/door.png", "textures/barrel 2.png"]; var wallTexture; var groundTexture; var ceilingTexture; var doorTexture; +var barrelTexture; Utils.loadImages(textureUrls).then(textures => { ctx.drawImage(textures[0], 0, 0); @@ -338,6 +409,9 @@ Utils.loadImages(textureUrls).then(textures => { ctx.drawImage(textures[3], 0, 0); doorTexture = ctx.getImageData(0, 0, textureWidth, textureHeight); - + + ctx.drawImage(textures[4], 0, 0); + barrelTexture = ctx.getImageData(0, 0, textureWidth, textureHeight); + requestAnimationFrame(main); }); \ No newline at end of file diff --git a/src/Player.js b/src/Player.js index 5947ad3..f5016c6 100644 --- a/src/Player.js +++ b/src/Player.js @@ -9,7 +9,7 @@ export class Player { * Creates an instance of Player. */ constructor() { - this.position = new Utils.Vector2(1.5, 1.5); + this.position = new Utils.Vector2(3.5, 1.5); this.direction = new Utils.Vector2(1, 0); this.plane = new Utils.Vector2(0, 0.66); this.walkTime = 0.0; @@ -78,7 +78,7 @@ export class Player { if (moved) { this.walkTime += timer.getDeltaTime(); - this.pitch += (Math.cos(10 * this.walkTime) / 2 * 8); + this.pitch += (Math.cos(10 * this.walkTime) / 2 * 7); } } diff --git a/src/Sprite.js b/src/Sprite.js new file mode 100644 index 0000000..2d4ebfc --- /dev/null +++ b/src/Sprite.js @@ -0,0 +1,8 @@ +export class Sprite { + constructor(position, texture) { + this.distance = 0.0; + + this.position = position; + this.texture = texture; + } +} \ No newline at end of file diff --git a/textures/barrel 2.png b/textures/barrel 2.png new file mode 100755 index 0000000000000000000000000000000000000000..eea212d222affad6dd9595e4dd046103cdad5d35 GIT binary patch literal 1132 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 HB{Ts5j9`gk literal 0 HcmV?d00001