From ad192fe7068081ed88f8616581bf450d601e99b1 Mon Sep 17 00:00:00 2001 From: James Barnett Date: Tue, 29 Dec 2020 20:13:01 +0000 Subject: Add textures --- src/main/kotlin/Extensions.kt | 4 +++ src/main/kotlin/Map.kt | 8 ++--- src/main/kotlin/RaycastContext.kt | 1 + src/main/kotlin/Raycaster.kt | 36 +++++++++++++++------ src/main/kotlin/Renderer.kt | 23 +++++++++----- src/main/kotlin/Texture.kt | 6 ++++ src/main/kotlin/TextureManager.kt | 49 +++++++++++++++++++++++++++++ src/main/kotlin/main.kt | 17 ++++++---- src/main/resources/index.html | 7 +++++ src/main/resources/textures/wolf/wall1.png | Bin 0 -> 13405 bytes src/main/resources/textures/wolf/wood1.png | Bin 0 -> 7533 bytes src/main/resources/textures/wolf/wood2.png | Bin 0 -> 1614 bytes 12 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 src/main/kotlin/Texture.kt create mode 100644 src/main/kotlin/TextureManager.kt create mode 100644 src/main/resources/textures/wolf/wall1.png create mode 100644 src/main/resources/textures/wolf/wood1.png create mode 100644 src/main/resources/textures/wolf/wood2.png diff --git a/src/main/kotlin/Extensions.kt b/src/main/kotlin/Extensions.kt index 989118f..8c11af1 100644 --- a/src/main/kotlin/Extensions.kt +++ b/src/main/kotlin/Extensions.kt @@ -4,4 +4,8 @@ fun Double.sine(): Double { fun Double.cosine(): Double { return kotlin.math.cos(toRadians(this)) +} + +fun Double.toFlooredInt(): Int { + return kotlin.math.floor(this).toInt() } \ No newline at end of file diff --git a/src/main/kotlin/Map.kt b/src/main/kotlin/Map.kt index d458a67..43c707d 100644 --- a/src/main/kotlin/Map.kt +++ b/src/main/kotlin/Map.kt @@ -3,10 +3,10 @@ class Map { listOf(1,1,1,1,1,1,1,1,1,1), listOf(1,0,0,0,0,0,0,0,0,1), listOf(1,0,0,0,0,0,0,0,0,1), - listOf(1,0,0,1,1,0,1,0,0,1), - listOf(1,0,0,1,0,0,1,0,0,1), - listOf(1,0,0,1,0,0,1,0,0,1), - listOf(1,0,0,1,0,1,1,0,0,1), + listOf(1,0,0,2,2,0,2,0,0,1), + listOf(1,0,0,2,0,0,2,0,0,1), + listOf(1,0,0,2,0,0,2,0,0,1), + listOf(1,0,0,2,0,2,2,0,0,1), listOf(1,0,0,0,0,0,0,0,0,1), listOf(1,0,0,0,0,0,0,0,0,1), listOf(1,1,1,1,1,1,1,1,1,1) diff --git a/src/main/kotlin/RaycastContext.kt b/src/main/kotlin/RaycastContext.kt index 9b4156c..808f3c4 100644 --- a/src/main/kotlin/RaycastContext.kt +++ b/src/main/kotlin/RaycastContext.kt @@ -1,5 +1,6 @@ data class RaycastContext( val renderer: Renderer, + val textureManager: TextureManager, val camera: Camera, val map: Map, val minimap: Minimap diff --git a/src/main/kotlin/Raycaster.kt b/src/main/kotlin/Raycaster.kt index 640ffef..8cc3781 100644 --- a/src/main/kotlin/Raycaster.kt +++ b/src/main/kotlin/Raycaster.kt @@ -1,9 +1,11 @@ +import kotlin.js.Date import kotlin.math.pow -class Raycaster(private val stepPrecision: Int = 32) { +class Raycaster(private val stepPrecision: Int) { fun raycast(raycastContext: RaycastContext) { - val (renderer, camera, map, _) = raycastContext + val raycastStartMs = Date().getTime() + val (renderer, textureManager, camera, map, _) = raycastContext val viewportWidth = renderer.viewportWidth val viewportHeight = renderer.viewportHeight @@ -15,28 +17,44 @@ class Raycaster(private val stepPrecision: Int = 32) { var rayX = camera.xPos var rayY = camera.yPos + var objectTypeHit: Int do { rayX += raySweepAngle.cosine() / stepPrecision rayY += raySweepAngle.sine() / stepPrecision + // TODO bounds checking - val wallHit = map.data[kotlin.math.floor(rayY).toInt()][kotlin.math.floor(rayX).toInt()] > 0 - } while (!wallHit) + objectTypeHit = map.data[rayY.toFlooredInt()][rayX.toFlooredInt()] + } while (objectTypeHit == 0) + + val texture = textureManager.getTexture(objectTypeHit) + val textureXIndex = ((texture.width * (rayX + rayY)) % texture.width).toFlooredInt() val distanceToWall = kotlin.math.sqrt((camera.xPos - rayX).pow(2) + (camera.yPos - rayY).pow(2)) - val wallHeight = kotlin.math.floor(viewportHeightHalf / distanceToWall).toInt() + val wallHeight = viewportHeightHalf / distanceToWall - // Sky - renderer.drawLine(rayIndex, 0, rayIndex, viewportHeightHalf - wallHeight, "cyan") + // Ceiling + renderer.drawLine(rayIndex, 0.0, rayIndex, viewportHeightHalf - wallHeight, "#505050") // Wall - renderer.drawLine(rayIndex, viewportHeightHalf - wallHeight, rayIndex, viewportHeightHalf + wallHeight, "red") + drawWallTexture(rayIndex, wallHeight, textureXIndex, texture, renderer) // Floor - renderer.drawLine(rayIndex, viewportHeightHalf + wallHeight, rayIndex, viewportHeight, "green") + renderer.drawLine(rayIndex, viewportHeightHalf + wallHeight, rayIndex, viewportHeight, "#A9A9A9") raySweepAngle += (camera.fov / viewportWidth.toDouble()) } + + console.log("Viewport raycast in ${Date().getTime() - raycastStartMs}ms") } + private fun drawWallTexture(rayIndex: Int, wallHeight: Double, textureXIndex: Int, texture: Texture, renderer: Renderer) { + val yIncrement = (wallHeight * 2) / texture.height + var y = (renderer.viewportHeight/2) - wallHeight + for (i in 0 until texture.height) { + // Extend y length by 0.1 to overlap strips slightly to avoid screen-door like effect + renderer.drawLine(rayIndex, y, rayIndex, y + yIncrement + 0.1, texture.cssColourPixelList[textureXIndex + i * texture.width]) + y += yIncrement + } + } } \ No newline at end of file diff --git a/src/main/kotlin/Renderer.kt b/src/main/kotlin/Renderer.kt index 8f71352..3a7111e 100644 --- a/src/main/kotlin/Renderer.kt +++ b/src/main/kotlin/Renderer.kt @@ -2,29 +2,38 @@ import kotlinx.browser.document import org.w3c.dom.CanvasRenderingContext2D import org.w3c.dom.HTMLCanvasElement -class Renderer(val viewportWidth: Int, val viewportHeight: Int) { +class Renderer(val viewportWidth: Int, val viewportHeight: Int, private val outputScale: Int) { private val canvas = (document.createElement("canvas") as HTMLCanvasElement) .apply { - width = viewportWidth - height = viewportHeight + width = viewportWidth * outputScale + height = viewportHeight * outputScale } private val context = canvas.getContext("2d") as CanvasRenderingContext2D init { + context.scale(outputScale.toDouble(), outputScale.toDouble()) document.body!!.appendChild(canvas) } - fun drawLine(startX: Int, startY: Int, endX: Int, endY: Int, cssColour: String = "#FF0000") { + fun drawLine(startX: Double, startY: Double, endX: Double, endY: Double, cssColour: String = "#FF0000") { context.strokeStyle = cssColour - context.lineWidth = 4.0 + context.lineWidth = 2.0 context.beginPath() - context.moveTo(startX.toDouble(), startY.toDouble()) - context.lineTo(endX.toDouble(), endY.toDouble()) + context.moveTo(startX, startY) + context.lineTo(endX, endY) context.stroke() } + fun drawLine(startX: Int, startY: Double, endX: Int, endY: Double, cssColour: String = "#FF0000") { + drawLine(startX.toDouble(), startY, endX.toDouble(), endY, cssColour) + } + + fun drawLine(startX: Int, startY: Double, endX: Int, endY: Int, cssColour: String = "#FF0000") { + drawLine(startX.toDouble(), startY, endX.toDouble(), endY.toDouble(), cssColour) + } + fun clear() { context.clearRect(0.0, 0.0, viewportWidth.toDouble(), viewportHeight.toDouble()) } diff --git a/src/main/kotlin/Texture.kt b/src/main/kotlin/Texture.kt new file mode 100644 index 0000000..b4ab881 --- /dev/null +++ b/src/main/kotlin/Texture.kt @@ -0,0 +1,6 @@ +data class Texture( + val id: Int, + val width: Int, + val height: Int, + val cssColourPixelList: List +) \ No newline at end of file diff --git a/src/main/kotlin/TextureManager.kt b/src/main/kotlin/TextureManager.kt new file mode 100644 index 0000000..58005e9 --- /dev/null +++ b/src/main/kotlin/TextureManager.kt @@ -0,0 +1,49 @@ +import kotlinx.browser.document +import org.khronos.webgl.get +import org.w3c.dom.CanvasRenderingContext2D +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLImageElement +import org.w3c.dom.asList + +class TextureManager { + + private lateinit var textures: List + + fun getTexture(id: Int): Texture { + return textures.first { it.id == id } + } + + fun loadTextures() { + textures = document.getElementsByClassName("texture-definition").asList() + .map { it as HTMLImageElement } + .map { + val id = it.id.toInt() + val width = it.getAttribute("data-width")?.toInt() ?: 64 + val height = it.getAttribute("data-height")?.toInt() ?: 64 + Texture(id, width, height, parseImage(it, width, height)) + } + + console.log("Loaded ${textures.size} texture(s)") + } + + private fun parseImage(image: HTMLImageElement, imageWidth: Int, imageHeight: Int): List { + val canvas = (document.createElement("canvas") as HTMLCanvasElement) + .apply { + width = imageWidth + height = imageHeight + } + + // Draw image to canvas and read back out to get RGBA data + val imageData = with(canvas.getContext("2d") as CanvasRenderingContext2D) { + drawImage(image, 0.0, 0.0, imageWidth.toDouble(), imageHeight.toDouble()) + getImageData(0.0, 0.0, imageWidth.toDouble(), imageHeight.toDouble()).data + } + + val cssColourPixelList = mutableListOf() + for (i in 0 until imageData.length step 4) { + cssColourPixelList.add("rgb(${imageData[i]},${imageData[i+1]},${imageData[i+2]}") + } + + return cssColourPixelList + } +} \ No newline at end of file diff --git a/src/main/kotlin/main.kt b/src/main/kotlin/main.kt index 65995e1..7533287 100644 --- a/src/main/kotlin/main.kt +++ b/src/main/kotlin/main.kt @@ -1,6 +1,7 @@ fun main() { - val raycaster = Raycaster() - val renderer = Renderer(viewportWidth = 640, viewportHeight = 480) + val renderer = Renderer(viewportWidth = 320, viewportHeight = 240, outputScale = 3) + val textureManager = TextureManager() + .apply { loadTextures() } val camera = Camera( fov = 90, xPos = 2.0, @@ -10,7 +11,9 @@ fun main() { val map = Map() val minimap = Minimap(map) - val context = RaycastContext(renderer, camera, map, minimap) + val context = RaycastContext(renderer, textureManager, camera, map, minimap) + + val raycaster = Raycaster(stepPrecision = 32) CameraController(camera, moveSpeed = 0.5, rotateSpeed = 5) { paint(raycaster, context) @@ -21,9 +24,11 @@ fun main() { } fun paint(raycaster: Raycaster, raycastContext: RaycastContext) { - raycastContext.renderer.clear() - raycaster.raycast(raycastContext) - raycastContext.minimap.update(raycastContext.camera) + with(raycastContext) { + renderer.clear() + raycaster.raycast(this) + minimap.update(camera) + } } fun toRadians(degrees: Double): Double { diff --git a/src/main/resources/index.html b/src/main/resources/index.html index 787422b..bbf13e6 100644 --- a/src/main/resources/index.html +++ b/src/main/resources/index.html @@ -3,8 +3,15 @@ Kotlin Raycaster + + + diff --git a/src/main/resources/textures/wolf/wall1.png b/src/main/resources/textures/wolf/wall1.png new file mode 100644 index 0000000..53cb30c Binary files /dev/null and b/src/main/resources/textures/wolf/wall1.png differ diff --git a/src/main/resources/textures/wolf/wood1.png b/src/main/resources/textures/wolf/wood1.png new file mode 100644 index 0000000..735b8ad Binary files /dev/null and b/src/main/resources/textures/wolf/wood1.png differ diff --git a/src/main/resources/textures/wolf/wood2.png b/src/main/resources/textures/wolf/wood2.png new file mode 100644 index 0000000..5f36e40 Binary files /dev/null and b/src/main/resources/textures/wolf/wood2.png differ -- cgit v1.2.3