aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJames Barnett <noreply@jamesbarnett.xyz>2020-12-29 20:13:01 +0000
committerJames Barnett <noreply@jamesbarnett.xyz>2020-12-29 20:13:01 +0000
commitad192fe7068081ed88f8616581bf450d601e99b1 (patch)
treee3cda400065c051ee628dbeb85709f778c90311e /src
parente816c727624056b91c5ef5152c3121a7f8497c5b (diff)
downloadkotlin-raycaster-ad192fe7068081ed88f8616581bf450d601e99b1.tar.xz
kotlin-raycaster-ad192fe7068081ed88f8616581bf450d601e99b1.zip
Add textures
Diffstat (limited to 'src')
-rw-r--r--src/main/kotlin/Extensions.kt4
-rw-r--r--src/main/kotlin/Map.kt8
-rw-r--r--src/main/kotlin/RaycastContext.kt1
-rw-r--r--src/main/kotlin/Raycaster.kt36
-rw-r--r--src/main/kotlin/Renderer.kt23
-rw-r--r--src/main/kotlin/Texture.kt6
-rw-r--r--src/main/kotlin/TextureManager.kt49
-rw-r--r--src/main/kotlin/main.kt17
-rw-r--r--src/main/resources/index.html7
-rw-r--r--src/main/resources/textures/wolf/wall1.pngbin0 -> 13405 bytes
-rw-r--r--src/main/resources/textures/wolf/wood1.pngbin0 -> 7533 bytes
-rw-r--r--src/main/resources/textures/wolf/wood2.pngbin0 -> 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
new file mode 100644
index 0000000..53cb30c
--- /dev/null
+++ b/src/main/resources/textures/wolf/wall1.png
Binary files 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
--- /dev/null
+++ b/src/main/resources/textures/wolf/wood1.png
Binary files 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
--- /dev/null
+++ b/src/main/resources/textures/wolf/wood2.png
Binary files differ