diff --git a/core/src/main/kotlin/dev/mtctx/foresst/resource/InputStreamFilePairs.kt b/core/src/main/kotlin/dev/mtctx/foresst/resource/InputStreamFilePairs.kt
new file mode 100644
index 0000000..7689143
--- /dev/null
+++ b/core/src/main/kotlin/dev/mtctx/foresst/resource/InputStreamFilePairs.kt
@@ -0,0 +1,24 @@
+/*
+ * Foresst: InputStreamFilePairs.kt
+ * Copyright (C) 2025 mtctx
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.resource
+
+object InputStreamFilePairs {
+ val Pair.inputStream get() = first
+ val Pair<*, Y>.file get() = second
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/dev/mtctx/foresst/resource/Resource.kt b/core/src/main/kotlin/dev/mtctx/foresst/resource/Resource.kt
new file mode 100644
index 0000000..9410930
--- /dev/null
+++ b/core/src/main/kotlin/dev/mtctx/foresst/resource/Resource.kt
@@ -0,0 +1,43 @@
+/*
+ * Foresst: Resource.kt
+ * Copyright (C) 2025 mtctx
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.resource
+
+import java.io.File
+import java.io.InputStream
+import java.nio.charset.Charset
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardCopyOption
+
+class Resource(resourcePath: String) {
+
+ private val inputStream: InputStream =
+ ResourceLoader.getResourceAsStream(resourcePath)
+ ?: throw IllegalStateException("Resource $resourcePath not found.")
+
+ private val tempFile: Path by lazy {
+ val tempFile = Files.createTempFile("foresst-", ".tmp")
+ tempFile.toFile().deleteOnExit()
+ Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING)
+ tempFile
+ }
+
+ val file: File
+ get() = tempFile.toFile()
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/dev/mtctx/foresst/resource/ResourceLoader.kt b/core/src/main/kotlin/dev/mtctx/foresst/resource/ResourceLoader.kt
new file mode 100644
index 0000000..908fc0f
--- /dev/null
+++ b/core/src/main/kotlin/dev/mtctx/foresst/resource/ResourceLoader.kt
@@ -0,0 +1,32 @@
+/*
+ * Foresst: ResourceLoader.kt
+ * Copyright (C) 2025 mtctx
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.resource
+
+import java.io.InputStream
+
+object ResourceLoader {
+
+ fun getResourceAsStream(resourcePath: String): InputStream? {
+ return ResourceLoader::class.java.classLoader.getResourceAsStream(resourcePath)
+ }
+}
+
+fun import(resourcePath: String): Resource {
+ return Resource(resourcePath)
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/dev/mtctx/foresst/resource/conversion.kt b/core/src/main/kotlin/dev/mtctx/foresst/resource/conversion.kt
new file mode 100644
index 0000000..4cb6791
--- /dev/null
+++ b/core/src/main/kotlin/dev/mtctx/foresst/resource/conversion.kt
@@ -0,0 +1,37 @@
+/*
+ * Foresst: conversion.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.resource
+
+import java.io.InputStream
+import java.nio.charset.Charset
+
+fun Resource.asText(charset: String = "UTF-8"): String =
+ file.readText(Charset.forName(charset))
+
+fun Resource.asByteArray(): ByteArray =
+ file.readBytes()
+
+fun Resource.asInputStream(): InputStream =
+ file.inputStream()
+
+fun Resource.asReader(charset: String = "UTF-8") =
+ file.reader(Charset.forName(charset))
+
+fun Resource.asLines(charset: String = "UTF-8"): Sequence =
+ file.useLines(Charset.forName(charset)) { it.toList() }.asSequence()
\ No newline at end of file
diff --git a/graphics/build.gradle.kts b/graphics/build.gradle.kts
index 24fecc1..b63ec37 100644
--- a/graphics/build.gradle.kts
+++ b/graphics/build.gradle.kts
@@ -81,6 +81,9 @@ dependencies {
implementation("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives)
implementation("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives)
implementation("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives)
+
+ implementation("io.github.kotlin-graphics:glm:0.9.9.1-12")
+
testImplementation(kotlin("test"))
}
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Graphics.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Graphics.kt
new file mode 100644
index 0000000..16c974c
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Graphics.kt
@@ -0,0 +1,86 @@
+/*
+ * Foresst: Graphics.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics
+
+import dev.mtctx.foresst.graphics.gl.GLShader
+import dev.mtctx.foresst.graphics.gl.GLShaderType
+import dev.mtctx.foresst.graphics.renderers.VoxelRenderer
+import dev.mtctx.foresst.resource.Resource
+import org.lwjgl.glfw.GLFW.*
+import org.lwjgl.glfw.GLFWErrorCallback
+import org.lwjgl.opengl.GL
+import org.lwjgl.opengl.GL46.*
+
+
+// TERRIBLE CODE AHEAD; WATCH YOUR STEP
+object Graphics {
+
+ lateinit var window: Window
+ lateinit var shader: GLShader
+ private val renderers = mutableListOf()
+
+ fun run() {
+ init()
+ render()
+ }
+
+ fun init() {
+ GLFWErrorCallback.createPrint(System.err).set()
+ check(glfwInit()) { "Unable to initialize GLFW" }
+ window = Window()
+ GL.createCapabilities()
+
+ val fragment = Resource("frag.fs")
+ val vertex = Resource("vert.vs")
+
+ shader = GLShader(
+ listOf(
+ GLShader.ShaderModule(
+ fragment,
+ GLShaderType.FRAGMENT
+ ),
+ GLShader.ShaderModule(
+ vertex,
+ GLShaderType.VERTEX
+ )
+ )
+ )
+
+ renderers.add(VoxelRenderer(shader))
+ }
+
+ fun render() {
+ while (!glfwWindowShouldClose(window.handle)) {
+ glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
+
+ val deltaTime = glfwGetTime().toFloat()
+
+ renderers.forEach {
+ it.update(deltaTime)
+ it.render()
+ }
+
+ glfwSwapBuffers(window.handle)
+ glfwPollEvents()
+ }
+
+ renderers.forEach(Renderer::close)
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Renderable.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Renderable.kt
new file mode 100644
index 0000000..9c29be6
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Renderable.kt
@@ -0,0 +1,27 @@
+/*
+ * Foresst: Renderable.kt
+ * Copyright (C) 2025 mtctx
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics
+
+import java.io.Closeable
+
+interface Renderable: Closeable {
+
+ fun render()
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Renderer.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Renderer.kt
new file mode 100644
index 0000000..7066707
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Renderer.kt
@@ -0,0 +1,26 @@
+/*
+ * Foresst: Renderer.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics
+
+interface Renderer : AutoCloseable {
+
+ fun render()
+ fun update(deltaTime: Float) {}
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Window.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Window.kt
new file mode 100644
index 0000000..903a39c
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/Window.kt
@@ -0,0 +1,63 @@
+/*
+ * Foresst: Window.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics
+
+import java.io.Closeable
+
+import org.lwjgl.glfw.Callbacks
+import org.lwjgl.glfw.GLFW.*
+import org.lwjgl.opengl.GL46.*
+import org.lwjgl.system.MemoryUtil
+
+class Window(
+ var width: Int = 800,
+ var height: Int = 600,
+ var handle: Long = -1
+) : Closeable {
+
+ init {
+ glfwDefaultWindowHints()
+ glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
+ glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE)
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4)
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6)
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
+
+ handle = glfwCreateWindow(width, height, "Keaengine", MemoryUtil.NULL, MemoryUtil.NULL)
+ if (handle == MemoryUtil.NULL) throw RuntimeException("Failed to create the GLFW window")
+
+ glfwMakeContextCurrent(handle)
+ glfwSwapInterval(1)
+
+ glfwShowWindow(handle)
+
+ glfwSetFramebufferSizeCallback(handle) { _: Long, newWidth: Int, newHeight: Int ->
+ this.width = newWidth
+ this.height = newHeight
+ glViewport(0, 0, width, height)
+ }
+ }
+
+ override fun close() {
+ Callbacks.glfwFreeCallbacks(handle)
+ glfwDestroyWindow(handle)
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLDataType.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLDataType.kt
new file mode 100644
index 0000000..057f34f
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLDataType.kt
@@ -0,0 +1,44 @@
+/*
+ * Foresst: GLDataType.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl
+
+import org.lwjgl.opengl.GL46.*
+
+enum class GLDataType(val size: Int, val glType: Int) {
+
+ Float(1, GL_FLOAT),
+ Integer(1, GL_INT),
+ Boolean(1, GL_BOOL),
+ Vec2(2, GL_FLOAT),
+ Vec3(3, GL_FLOAT),
+ Vec4(4, GL_FLOAT),
+ Mat2(2 * 2, GL_FLOAT),
+ Mat3(3 * 3, GL_FLOAT),
+ Mat4(4 * 4, GL_FLOAT);
+
+ fun byteSize(): Int =
+ size * when (glType) {
+ GL_INT -> Int.SIZE_BYTES
+ GL_FLOAT -> kotlin.Float.SIZE_BYTES
+ GL_DOUBLE -> Double.SIZE_BYTES
+ GL_BOOL -> Byte.SIZE_BYTES
+ else -> throw IllegalArgumentException("Unsupported GL type: $glType")
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLShader.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLShader.kt
new file mode 100644
index 0000000..eea857c
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLShader.kt
@@ -0,0 +1,189 @@
+/*
+ * Foresst: GLShader.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl
+
+import dev.mtctx.foresst.graphics.Renderable
+import dev.mtctx.foresst.resource.Resource
+import dev.mtctx.foresst.resource.asText
+import glm_.mat2x2.Mat2
+import glm_.mat3x3.Mat3
+import glm_.mat4x4.Mat4
+import glm_.vec2.Vec2
+import glm_.vec3.Vec3
+import glm_.vec4.Vec4
+import org.lwjgl.opengl.GL46.*
+
+class GLShader(
+ modules: List
+) : Renderable {
+
+ private var program: Int = 0
+ private val uniformLocationCache = mutableMapOf()
+
+ init {
+ if (modules.isEmpty()) throw IllegalArgumentException("No shader modules provided")
+
+ val duplicates = modules.groupingBy { it.shaderType }
+ .eachCount()
+ .filterValues { it > 1 }
+ .keys
+
+ if (duplicates.isNotEmpty()) {
+ throw IllegalArgumentException("Duplicate shader modules for types: ${duplicates.joinToString()}")
+ }
+
+ program = glCreateProgram().also { if (it == 0) throw RuntimeException("glCreateProgram returned 0") }
+
+ val shaderIds = mutableListOf()
+ try {
+ for (module in modules) {
+ val shaderId = compileModule(module)
+ shaderIds += shaderId
+ glAttachShader(program, shaderId)
+ }
+
+ glLinkProgram(program)
+ val linkStatus = glGetProgrami(program, GL_LINK_STATUS)
+ if (linkStatus == GL_FALSE) {
+ val infoLog = glGetProgramInfoLog(program)
+ throw RuntimeException("Failed to link program: $infoLog")
+ }
+
+ for (id in shaderIds) {
+ glDetachShader(program, id)
+ glDeleteShader(id)
+ }
+
+ } catch (e: Exception) {
+ for (id in shaderIds) {
+ if (glIsShader(id)) glDeleteShader(id)
+ }
+ if (glIsProgram(program)) {
+ glDeleteProgram(program)
+ program = 0
+ }
+ throw e
+ }
+ }
+
+ private fun compileModule(module: ShaderModule): Int {
+ val shaderId = glCreateShader(module.shaderType.id)
+ if (shaderId == 0) throw RuntimeException("glCreateShader returned 0 for type=${module.shaderType}")
+
+ glShaderSource(shaderId, module.res.asText())
+ glCompileShader(shaderId)
+
+ val status = glGetShaderi(shaderId, GL_COMPILE_STATUS)
+ if (status == GL_FALSE) {
+ val infoLog = glGetShaderInfoLog(shaderId)
+ glDeleteShader(shaderId)
+ throw RuntimeException("Failed to compile shader (${module.name ?: module.shaderType}): $infoLog")
+ }
+
+ return shaderId
+ }
+
+ fun use(block: Context.() -> Unit) {
+ bind()
+ try {
+ Context(this).block()
+ } finally {
+ unbind()
+ }
+ }
+
+ fun bind() {
+ if (program == 0) throw IllegalStateException("Program has been deleted")
+ glUseProgram(program)
+ }
+
+ fun unbind() {
+ glUseProgram(0)
+ }
+
+ override fun render() {
+ bind()
+ }
+
+ override fun close() {
+ unbind()
+ }
+
+ fun delete() {
+ if (program != 0 && glIsProgram(program)) {
+ glDeleteProgram(program)
+ program = 0
+ }
+ }
+
+ data class ShaderModule(val res: Resource, val shaderType: GLShaderType, val name: String? = null)
+
+ class Context internal constructor(private val shader: GLShader) {
+
+ private fun getLocation(name: String): Int =
+ shader.uniformLocationCache.getOrPut(name) {
+ glGetUniformLocation(shader.program, name)
+ }
+
+ fun uniformInt(name: String, value: Int) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniform1i(loc, value)
+ }
+
+ fun uniformFloat(name: String, value: Float) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniform1f(loc, value)
+ }
+
+ fun uniformVec2(name: String, value: Vec2) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniform2f(loc, value.x, value.y)
+ }
+
+ fun uniformVec3(name: String, value: Vec3) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniform3f(loc, value.x, value.y, value.z)
+ }
+
+ fun uniformVec4(name: String, value: Vec4) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniform4f(loc, value.x, value.y, value.z, value.w)
+ }
+
+ fun uniformMat2(name: String, value: Mat2) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniformMatrix2fv(loc, false, value.array)
+ }
+
+ fun uniformMat3(name: String, value: Mat3) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniformMatrix3fv(loc, false, value.array)
+ }
+
+ fun uniformMat4(name: String, value: Mat4) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniformMatrix4fv(loc, false, value.array)
+ }
+
+ fun uniformIntArray(name: String, values: IntArray) {
+ val loc = getLocation(name)
+ if (loc != -1) glUniform1iv(loc, values)
+ }
+ }
+}
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLShaderType.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLShaderType.kt
new file mode 100644
index 0000000..4a2acb6
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLShaderType.kt
@@ -0,0 +1,26 @@
+/*
+ * Foresst: GLShaderType.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl
+
+import org.lwjgl.opengl.GL20
+
+enum class GLShaderType(val id : Int) {
+ FRAGMENT(GL20.GL_FRAGMENT_SHADER),
+ VERTEX(GL20.GL_VERTEX_SHADER)
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLVertexArray.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLVertexArray.kt
new file mode 100644
index 0000000..9bb1d71
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/GLVertexArray.kt
@@ -0,0 +1,96 @@
+/*
+ * Foresst: GLVertexArray.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl
+
+import dev.mtctx.foresst.graphics.Renderable
+import dev.mtctx.foresst.graphics.gl.buffer.GLIndexBuffer
+import dev.mtctx.foresst.graphics.gl.buffer.GLVertexBuffer
+import org.lwjgl.opengl.GL46.*
+
+class GLVertexArray(private val indexBuffer: GLIndexBuffer? = null, private val mode: Int = GL_TRIANGLES) : Renderable {
+
+ private var id: Int = 0
+ private val vertexBuffers = mutableListOf()
+
+ init {
+ id = glCreateVertexArrays()
+ glBindVertexArray(id)
+
+ indexBuffer?.bind()
+
+ // unbind after setup
+ glBindVertexArray(0)
+ }
+
+ fun addVertexBuffer(vertexBuffer: GLVertexBuffer) {
+ if (vertexBuffer in vertexBuffers) return
+
+ bind()
+ vertexBuffer.bind()
+
+ check(vertexBuffer.layout.bufferElements.isNotEmpty()) { "Layout contains no elements" }
+
+ vertexBuffer.layout.bufferElements.forEachIndexed { index, element ->
+ glEnableVertexAttribArray(index)
+ glVertexAttribPointer(
+ index,
+ element.dataType.size,
+ element.dataType.glType,
+ false,
+ vertexBuffer.layout.stride,
+ element.offset.toLong()
+ )
+ }
+
+ vertexBuffers += vertexBuffer
+ }
+
+ fun removeVertexBuffer(vertexBuffer: GLVertexBuffer) {
+ bind()
+ vertexBuffer.bind()
+ vertexBuffer.close()
+
+ vertexBuffers -= vertexBuffer
+ }
+
+ fun bind() {
+ glBindVertexArray(id)
+ }
+
+ override fun render() {
+ bind()
+
+ if (indexBuffer != null && indexBuffer.count > 0) {
+ glDrawElements(mode, indexBuffer.count, GL_UNSIGNED_INT, 0)
+ } else {
+ vertexBuffers.forEach { vertexBuffer ->
+ glDrawArrays(mode, 0, vertexBuffer.count)
+ }
+ }
+ }
+
+ override fun close() {
+ glBindVertexArray(0)
+
+ vertexBuffers.forEach(GLVertexBuffer::close)
+ indexBuffer?.close()
+ glDeleteVertexArrays(id)
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLBufferElement.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLBufferElement.kt
new file mode 100644
index 0000000..8c14845
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLBufferElement.kt
@@ -0,0 +1,27 @@
+/*
+ * Foresst: GLBufferElement.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl.buffer
+
+import dev.mtctx.foresst.graphics.gl.GLDataType
+
+data class GLBufferElement(
+ val name: String,
+ val dataType: GLDataType,
+ var offset: Int = 0
+)
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLBufferLayout.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLBufferLayout.kt
new file mode 100644
index 0000000..a2f2d63
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLBufferLayout.kt
@@ -0,0 +1,49 @@
+/*
+ * Foresst: GLBufferLayout.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl.buffer
+
+import dev.mtctx.foresst.graphics.gl.GLDataType
+
+class GLBufferLayout(map: Map) {
+
+ var stride: Int = 0
+ var bufferElements = mutableListOf()
+
+ init {
+ require(map.isNotEmpty()) { "Can't create buffer layout with empty map" }
+
+ calculateOffsets(map)
+
+ // stride represents the byte offset between consecutive elements within the buffer
+ stride =
+ bufferElements.lastOrNull()?.let {
+ it.offset + it.dataType.byteSize()
+ } ?: 0
+ }
+
+ private fun calculateOffsets(map: Map) {
+ var offset = 0
+ map.forEach { (name, dataType) ->
+ val bufferElement = GLBufferElement(name, dataType, offset)
+ bufferElements += bufferElement
+ offset += dataType.byteSize()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLIndexBuffer.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLIndexBuffer.kt
new file mode 100644
index 0000000..ac9bf08
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLIndexBuffer.kt
@@ -0,0 +1,55 @@
+/*
+ * Foresst: GLIndexBuffer.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl.buffer
+
+import dev.mtctx.foresst.graphics.Renderable
+import org.lwjgl.opengl.GL46.*
+
+class GLIndexBuffer(indices: IntArray) : Renderable {
+
+ private var id: Int = 0
+ var count: Int = 0
+
+ init {
+ count = indices.size * Int.SIZE_BYTES
+
+ id = glCreateBuffers()
+ bind()
+
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
+ }
+
+ fun bind() {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id)
+ }
+
+ fun clear() {
+ glDeleteBuffers(id)
+ }
+
+ override fun render() {
+ bind()
+ }
+
+ override fun close() {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
+ clear()
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLVertexBuffer.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLVertexBuffer.kt
new file mode 100644
index 0000000..c4c2114
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/gl/buffer/GLVertexBuffer.kt
@@ -0,0 +1,54 @@
+/*
+ * Foresst: GLVertexBuffer.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.gl.buffer
+
+import dev.mtctx.foresst.graphics.Renderable
+import org.lwjgl.opengl.GL46.*
+
+class GLVertexBuffer(vertices: FloatArray, val layout: GLBufferLayout) : Renderable {
+
+ private var id: Int = 0
+ var count: Int = vertices.size
+
+ init {
+ id = glCreateBuffers()
+ bind()
+
+ glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW)
+ }
+
+ fun bind() {
+ glBindBuffer(GL_ARRAY_BUFFER, id)
+ }
+
+ fun update(vertices: FloatArray) {
+ bind()
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vertices)
+ count = vertices.size
+ }
+
+ override fun render() {
+ bind()
+ }
+
+ override fun close() {
+ glDeleteBuffers(id)
+ }
+
+}
\ No newline at end of file
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/renderers/VoxelRenderer.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/renderers/VoxelRenderer.kt
new file mode 100644
index 0000000..964f360
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/renderers/VoxelRenderer.kt
@@ -0,0 +1,86 @@
+/*
+ * Foresst: VoxelRenderer.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.renderers
+
+import dev.mtctx.foresst.graphics.Renderer
+import dev.mtctx.foresst.graphics.gl.GLShader
+import dev.mtctx.foresst.graphics.gl.GLVertexArray
+import dev.mtctx.foresst.graphics.gl.buffer.GLBufferLayout
+import dev.mtctx.foresst.graphics.gl.buffer.GLVertexBuffer
+import dev.mtctx.foresst.graphics.gl.buffer.GLIndexBuffer
+import dev.mtctx.foresst.graphics.gl.GLDataType
+import glm_.glm
+import glm_.mat4x4.Mat4
+import glm_.vec3.Vec3
+
+class VoxelRenderer(private val shader: GLShader) : Renderer {
+
+ private val vao: GLVertexArray
+
+ private var model: Mat4 = Mat4(1.0f)
+ private var view: Mat4 = glm.lookAt(
+ Vec3(2f, 2f, 2f),
+ Vec3(0f, 0f, 0f),
+ Vec3(0f, 1f, 0f)
+ )
+ private var projection: Mat4 = glm.perspective(glm.radians(45f), 800f / 600f, 0.1f, 100f)
+
+ init {
+ // Cube vertices
+ val vertices = floatArrayOf(
+ -0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f,
+ -0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f
+ )
+
+ val indices = intArrayOf(
+ 0, 1, 2, 2, 3, 0,
+ 4, 5, 6, 6, 7, 4,
+ 0, 1, 5, 5, 4, 0,
+ 2, 3, 7, 7, 6, 2,
+ 0, 3, 7, 7, 4, 0,
+ 1, 2, 6, 6, 5, 1
+ )
+
+ val layout = GLBufferLayout(mapOf("position" to GLDataType.Vec3))
+ val vbo = GLVertexBuffer(vertices, layout)
+ val ibo = GLIndexBuffer(indices)
+
+ vao = GLVertexArray(ibo)
+ vao.addVertexBuffer(vbo)
+ }
+
+ override fun render() {
+ shader.use {
+ val mvp = projection * view * model
+ uniformMat4("mvp", mvp)
+
+ vao.render()
+ }
+ }
+
+ override fun close() {
+ vao.close()
+ }
+}
diff --git a/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/utils/TranslationUtils.kt b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/utils/TranslationUtils.kt
new file mode 100644
index 0000000..82f3122
--- /dev/null
+++ b/graphics/src/main/kotlin/dev/mtctx/foresst/graphics/utils/TranslationUtils.kt
@@ -0,0 +1,32 @@
+/*
+ * Foresst: TranslationUtils.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package dev.mtctx.foresst.graphics.utils
+
+import glm_.func.toRadians
+import glm_.mat4x4.Mat4
+import glm_.vec3.Vec3
+
+fun calculateModelMatrix(pos: Vec3, rotation: Vec3): Mat4 =
+ Mat4().identity().translateAssign(pos)
+ .rotateXassign(toRadians(-rotation.x))
+ .rotateYassign(toRadians(-rotation.y))
+ .rotateZassign(toRadians(-rotation.z))
+
+fun calculateModelMatrix(): Mat4 =
+ calculateModelMatrix(Vec3(0, 0, 0), Vec3(0, 0, 0))
\ No newline at end of file
diff --git a/graphics/src/main/resources/frag.fs b/graphics/src/main/resources/frag.fs
new file mode 100644
index 0000000..dad22a9
--- /dev/null
+++ b/graphics/src/main/resources/frag.fs
@@ -0,0 +1,7 @@
+#version 330 core
+
+out vec4 FragColor;
+
+void main() {
+ FragColor = vec4(1.0, 1.0, 1.0, 1.0);
+}
diff --git a/graphics/src/main/resources/vert.vs b/graphics/src/main/resources/vert.vs
new file mode 100644
index 0000000..0790cc3
--- /dev/null
+++ b/graphics/src/main/resources/vert.vs
@@ -0,0 +1,9 @@
+#version 330 core
+
+layout(location = 0) in vec3 aPos;
+
+uniform mat4 mvp;
+
+void main() {
+ gl_Position = mvp * vec4(aPos, 1.0);
+}
diff --git a/graphics/src/test/kotlin/main.kt b/graphics/src/test/kotlin/main.kt
new file mode 100644
index 0000000..8371737
--- /dev/null
+++ b/graphics/src/test/kotlin/main.kt
@@ -0,0 +1,24 @@
+import dev.mtctx.foresst.graphics.Graphics
+
+/*
+ * Foresst: main.kt
+ * Copyright (C) 2025 mtctx, kvxd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+fun main(args: Array) {
+ Graphics
+ .run()
+}
\ No newline at end of file