aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/kotlin/Camera.kt8
-rw-r--r--src/main/kotlin/Minimap.kt57
-rw-r--r--src/main/kotlin/Renderer.kt31
-rw-r--r--src/main/kotlin/raycaster.kt117
-rw-r--r--src/main/resources/index.html10
5 files changed, 223 insertions, 0 deletions
diff --git a/src/main/kotlin/Camera.kt b/src/main/kotlin/Camera.kt
new file mode 100644
index 0000000..7d4bcfc
--- /dev/null
+++ b/src/main/kotlin/Camera.kt
@@ -0,0 +1,8 @@
+class Camera(
+ val fov: Int,
+ var xPos: Double,
+ var yPos: Double,
+ var rotation: Double
+) {
+ val halfFov = fov / 2
+} \ No newline at end of file
diff --git a/src/main/kotlin/Minimap.kt b/src/main/kotlin/Minimap.kt
new file mode 100644
index 0000000..dc7cc91
--- /dev/null
+++ b/src/main/kotlin/Minimap.kt
@@ -0,0 +1,57 @@
+import kotlinx.browser.document
+import org.w3c.dom.CanvasRenderingContext2D
+import org.w3c.dom.HTMLCanvasElement
+
+class Minimap(private val map: List<List<Int>>) {
+
+ private val scale = 30
+ private val mapWidth = map[0].size
+ private val mapHeight = map.size
+
+ private val canvas = (document.createElement("canvas") as HTMLCanvasElement)
+ .apply {
+ width = mapWidth * scale
+ height = mapHeight * scale
+ id = "minimap"
+ style.width = "${width}px"
+ style.height = "${height}px"
+ }
+ private val context = canvas.getContext("2d") as CanvasRenderingContext2D
+
+ init {
+ document.body!!.appendChild(canvas)
+ }
+
+ private fun drawMap() {
+ for (y in 0 until mapHeight) {
+ for (x in 0 until mapWidth) {
+ val wall = map[y][x]
+ if (wall > 0) {
+ context.fillStyle = "#000000"
+ context.fillRect((x * scale).toDouble(), (y * scale).toDouble(), scale.toDouble(), scale.toDouble())
+ }
+ }
+ }
+ }
+
+ fun update(camera: Camera) {
+ context.clearRect(0.0, 0.0, (mapWidth * scale).toDouble(), (mapHeight * scale).toDouble())
+ drawMap()
+ context.fillStyle = "#FF0000"
+ context.fillRect(camera.xPos * scale, camera.yPos * scale, scale.toDouble(), scale.toDouble())
+
+ context.strokeStyle = "#00FF00"
+ context.lineWidth = 2.0
+ context.beginPath()
+ context.moveTo(camera.xPos * scale, camera.yPos * scale)
+
+ val cameraCos = kotlin.math.cos(toRadians(camera.rotation)) * 4
+ val cameraSin = kotlin.math.sin(toRadians(camera.rotation)) * 4
+
+ val dirX = camera.xPos + cameraCos
+ val dirY = camera.yPos + cameraSin
+
+ context.lineTo(dirX * scale, dirY * scale)
+ context.stroke()
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/Renderer.kt b/src/main/kotlin/Renderer.kt
new file mode 100644
index 0000000..b266e61
--- /dev/null
+++ b/src/main/kotlin/Renderer.kt
@@ -0,0 +1,31 @@
+import kotlinx.browser.document
+import org.w3c.dom.CanvasRenderingContext2D
+import org.w3c.dom.HTMLCanvasElement
+
+class Renderer(val w: Int, val h: Int) {
+
+ private val canvas = (document.createElement("canvas") as HTMLCanvasElement)
+ .apply {
+ width = w
+ height = h
+ }
+ private val context = canvas.getContext("2d") as CanvasRenderingContext2D
+
+ init {
+ document.body!!.appendChild(canvas)
+ }
+
+ fun drawLine(startX: Int, startY: Int, endX: Int, endY: Int, cssColour: String = "#FF0000") {
+ context.strokeStyle = cssColour
+ context.lineWidth = 4.0
+ context.beginPath()
+ context.moveTo(startX.toDouble(), startY.toDouble())
+ context.lineTo(endX.toDouble(), endY.toDouble())
+ context.stroke()
+ }
+
+ fun clear() {
+ context.clearRect(0.0, 0.0, w.toDouble(), h.toDouble())
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/raycaster.kt b/src/main/kotlin/raycaster.kt
new file mode 100644
index 0000000..5b717bc
--- /dev/null
+++ b/src/main/kotlin/raycaster.kt
@@ -0,0 +1,117 @@
+import kotlinx.browser.document
+import kotlin.math.pow
+
+private const val SCREEN_WIDTH = 640
+private const val SCREEN_HEIGHT = 480
+private const val MOVE_SPEED = 0.5
+private const val ROTATE_SPEED = 5
+
+private val map = listOf(
+ 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,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)
+)
+
+data class RaycastContext(
+ val renderer: Renderer,
+ val camera: Camera,
+ val minimap: Minimap
+)
+
+fun main() {
+ val renderer = Renderer(SCREEN_WIDTH, SCREEN_HEIGHT)
+ val camera = Camera(
+ fov = 60,
+ xPos = 2.0,
+ yPos = 2.0,
+ rotation = 90.0
+ )
+ val minimap = Minimap(map)
+
+ val context = RaycastContext(renderer, camera, minimap)
+
+ document.onkeydown = {
+ when (it.code) {
+ "KeyW" -> {
+ console.log("key w")
+ val cameraCos = kotlin.math.cos(toRadians(camera.rotation)) * MOVE_SPEED
+ val cameraSin = kotlin.math.sin(toRadians(camera.rotation)) * MOVE_SPEED
+ camera.xPos += cameraCos
+ camera.yPos += cameraSin
+ }
+ "KeyS" -> {
+ console.log("key s")
+ val cameraCos = kotlin.math.cos(toRadians(camera.rotation)) * MOVE_SPEED
+ val cameraSin = kotlin.math.sin(toRadians(camera.rotation)) * MOVE_SPEED
+ camera.xPos -= cameraCos
+ camera.yPos -= cameraSin
+ }
+ "KeyA" -> {
+ console.log("key a")
+ camera.rotation -= ROTATE_SPEED
+ }
+ "KeyD" -> {
+ console.log("key d")
+ camera.rotation += ROTATE_SPEED
+ }
+ }
+ paint(context)
+ console.log("camera x:${camera.xPos} y:${camera.yPos} r: ${camera.rotation}")
+ }
+
+ paint(context)
+}
+
+fun paint(raycastContext: RaycastContext) {
+ raycastContext.renderer.clear()
+ raycast(raycastContext)
+ raycastContext.minimap.update(raycastContext.camera)
+}
+
+fun raycast(raycastContext: RaycastContext) {
+ val incrementAngle : Double = raycastContext.camera.fov / SCREEN_WIDTH.toDouble()
+ val rayCastPrecision = 32
+
+ var rayAngle = raycastContext.camera.rotation - raycastContext.camera.halfFov
+
+ for (rayIndex in 0 until SCREEN_WIDTH) {
+
+ var rayX = raycastContext.camera.xPos
+ var rayY = raycastContext.camera.yPos
+
+ val rayCos = kotlin.math.cos(toRadians(rayAngle)) / rayCastPrecision
+ val raySin = kotlin.math.sin(toRadians(rayAngle)) / rayCastPrecision
+
+ var isWall = false
+ while(!isWall) {
+ rayX += rayCos
+ rayY += raySin
+
+ // TODO bounds checking
+ val foo = map[kotlin.math.floor(rayY).toInt()][kotlin.math.floor(rayX).toInt()]
+ if (foo == 1) {
+ isWall = true
+ }
+ }
+
+ val distance = kotlin.math.sqrt((raycastContext.camera.xPos - rayX).pow(2) + (raycastContext.camera.yPos - rayY).pow(2))
+ val wallHeight = kotlin.math.floor((SCREEN_HEIGHT/2) / distance).toInt()
+
+ raycastContext.renderer.drawLine(rayIndex, 0, rayIndex, (SCREEN_HEIGHT/2)-wallHeight, "cyan")
+ raycastContext.renderer.drawLine(rayIndex, (SCREEN_HEIGHT/2)-wallHeight, rayIndex, (SCREEN_HEIGHT/2)+wallHeight, "red")
+ raycastContext.renderer.drawLine(rayIndex, (SCREEN_HEIGHT/2)+wallHeight, rayIndex, SCREEN_HEIGHT, "green")
+
+ rayAngle += incrementAngle
+ }
+}
+
+fun toRadians(degrees: Double): Double {
+ return degrees * kotlin.math.PI / 180
+}
diff --git a/src/main/resources/index.html b/src/main/resources/index.html
new file mode 100644
index 0000000..787422b
--- /dev/null
+++ b/src/main/resources/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>Kotlin Raycaster</title>
+ </head>
+ <body>
+ <script src="kotlin-raycaster.js"></script>
+ </body>
+</html>