diff options
| author | James Barnett <noreply@jamesbarnett.xyz> | 2020-12-29 20:13:01 +0000 |
|---|---|---|
| committer | James Barnett <noreply@jamesbarnett.xyz> | 2020-12-29 20:13:01 +0000 |
| commit | ad192fe7068081ed88f8616581bf450d601e99b1 (patch) | |
| tree | e3cda400065c051ee628dbeb85709f778c90311e /src | |
| parent | e816c727624056b91c5ef5152c3121a7f8497c5b (diff) | |
| download | kotlin-raycaster-ad192fe7068081ed88f8616581bf450d601e99b1.tar.xz kotlin-raycaster-ad192fe7068081ed88f8616581bf450d601e99b1.zip | |
Add textures
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/kotlin/Extensions.kt | 4 | ||||
| -rw-r--r-- | src/main/kotlin/Map.kt | 8 | ||||
| -rw-r--r-- | src/main/kotlin/RaycastContext.kt | 1 | ||||
| -rw-r--r-- | src/main/kotlin/Raycaster.kt | 36 | ||||
| -rw-r--r-- | src/main/kotlin/Renderer.kt | 23 | ||||
| -rw-r--r-- | src/main/kotlin/Texture.kt | 6 | ||||
| -rw-r--r-- | src/main/kotlin/TextureManager.kt | 49 | ||||
| -rw-r--r-- | src/main/kotlin/main.kt | 17 | ||||
| -rw-r--r-- | src/main/resources/index.html | 7 | ||||
| -rw-r--r-- | src/main/resources/textures/wolf/wall1.png | bin | 0 -> 13405 bytes | |||
| -rw-r--r-- | src/main/resources/textures/wolf/wood1.png | bin | 0 -> 7533 bytes | |||
| -rw-r--r-- | src/main/resources/textures/wolf/wood2.png | bin | 0 -> 1614 bytes |
12 files changed, 125 insertions, 26 deletions
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<String> +)
\ 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<Texture> + + 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<String> { + 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<String>() + 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 @@ <head> <meta charset="UTF-8"> <title>Kotlin Raycaster</title> + <style> + .texture-definition { + display: none + } + </style> </head> <body> + <img id="1" src="textures/wolf/wall1.png" data-width="64" data-height="64" class="texture-definition"> + <img id="2" src="textures/wolf/wood1.png" data-width="64" data-height="64" class="texture-definition"> <script src="kotlin-raycaster.js"></script> </body> </html> diff --git a/src/main/resources/textures/wolf/wall1.png b/src/main/resources/textures/wolf/wall1.png Binary files differnew file mode 100644 index 0000000..53cb30c --- /dev/null +++ b/src/main/resources/textures/wolf/wall1.png diff --git a/src/main/resources/textures/wolf/wood1.png b/src/main/resources/textures/wolf/wood1.png Binary files differnew file mode 100644 index 0000000..735b8ad --- /dev/null +++ b/src/main/resources/textures/wolf/wood1.png diff --git a/src/main/resources/textures/wolf/wood2.png b/src/main/resources/textures/wolf/wood2.png Binary files differnew file mode 100644 index 0000000..5f36e40 --- /dev/null +++ b/src/main/resources/textures/wolf/wood2.png |