mirror of
https://github.com/Ugric/Buildtopia.git
synced 2026-10-28 09:43:46 +00:00
add actual game logic and classes, chunk and other logic
This commit is contained in:
1 parent
6ef6f7f372
commit
d756a2eac2
18 files changed
+924
-312
No files matched your search
+4
-1
@@ -42,4 +42,7 @@ bin/
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
|
||||
app/src/main/resources/resource_pack
|
||||
Generated
+1
@@ -3,6 +3,7 @@
|
||||
<words>
|
||||
<w>blocktopia</w>
|
||||
<w>coords</w>
|
||||
<w>renderdistance</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
Generated
+1
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -14,6 +14,8 @@ dependencies {
|
||||
|
||||
implementation("org.joml:joml:1.10.5")
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
||||
|
||||
// Core LWJGL modules
|
||||
implementation("org.lwjgl:lwjgl:$lwjglVersion")
|
||||
implementation("org.lwjgl:lwjgl-opengl:$lwjglVersion")
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.minecraft.client.main;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
dev.wbell.buildtopia.app.AppKt.main();
|
||||
}
|
||||
}
|
||||
+126
-310
@@ -1,58 +1,12 @@
|
||||
package dev.wbell.buildtopia.app
|
||||
|
||||
import dev.wbell.buildtopia.app.game.Game
|
||||
import org.joml.Matrix4d
|
||||
import org.joml.Matrix4f
|
||||
import org.joml.Vector3d
|
||||
import org.joml.Vector3f
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
import org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray
|
||||
import org.lwjgl.opengl.ARBVertexArrayObject.glGenVertexArrays
|
||||
import org.lwjgl.opengl.GL
|
||||
import org.lwjgl.opengl.GL11.*
|
||||
import org.lwjgl.opengl.GL20.*
|
||||
import org.lwjgl.system.MemoryUtil.NULL
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import org.lwjgl.stb.STBImage
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.IntBuffer
|
||||
|
||||
fun loadTexture(path: String): Int {
|
||||
val textureId = glGenTextures()
|
||||
glBindTexture(GL_TEXTURE_2D, textureId)
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||
|
||||
// Load image from resources
|
||||
val stream = object {}.javaClass.getResourceAsStream(path)
|
||||
?: throw RuntimeException("Failed to load texture: $path")
|
||||
|
||||
// Read stream into a ByteBuffer
|
||||
val bytes = stream.readBytes()
|
||||
val buffer = BufferUtils.createByteBuffer(bytes.size)
|
||||
buffer.put(bytes)
|
||||
buffer.flip()
|
||||
|
||||
val widthBuffer: IntBuffer = BufferUtils.createIntBuffer(1)
|
||||
val heightBuffer: IntBuffer = BufferUtils.createIntBuffer(1)
|
||||
val channelsBuffer: IntBuffer = BufferUtils.createIntBuffer(1)
|
||||
|
||||
val image = STBImage.stbi_load_from_memory(buffer, widthBuffer, heightBuffer, channelsBuffer, 4)
|
||||
?: throw RuntimeException("Failed to load texture: ${STBImage.stbi_failure_reason()}")
|
||||
|
||||
val width = widthBuffer.get(0)
|
||||
val height = heightBuffer.get(0)
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
|
||||
STBImage.stbi_image_free(image)
|
||||
|
||||
return textureId
|
||||
}
|
||||
|
||||
fun loadResource(path: String): String {
|
||||
return object {}.javaClass.getResource(path)?.readText() ?: throw RuntimeException("Resource not found: $path")
|
||||
@@ -105,278 +59,140 @@ fun updateProjection(shaderProgram: Int, width: Int, height: Int) {
|
||||
glUniformMatrix4fv(projLoc, false, projBuffer)
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, cubeScale: Float, face: String) {
|
||||
val half = cubeScale / 2f
|
||||
when(face) {
|
||||
"front" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z + half, 0f, 0f, // bottom-left
|
||||
x + half, y - half, z + half, 1f, 0f, // bottom-right
|
||||
x + half, y + half, z + half, 1f, 1f, // top-right
|
||||
|
||||
x + half, y + half, z + half, 1f, 1f, // top-right
|
||||
x - half, y + half, z + half, 0f, 1f, // top-left
|
||||
x - half, y - half, z + half, 0f, 0f // bottom-left
|
||||
))
|
||||
"back" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z - half, 0f, 0f,
|
||||
x + half, y - half, z - half, 1f, 0f,
|
||||
x + half, y + half, z - half, 1f, 1f,
|
||||
|
||||
x + half, y + half, z - half, 1f, 1f,
|
||||
x - half, y + half, z - half, 0f, 1f,
|
||||
x - half, y - half, z - half, 0f, 0f
|
||||
))
|
||||
"left" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z - half, 0f, 0f,
|
||||
x - half, y - half, z + half, 1f, 0f,
|
||||
x - half, y + half, z + half, 1f, 1f,
|
||||
|
||||
x - half, y + half, z + half, 1f, 1f,
|
||||
x - half, y + half, z - half, 0f, 1f,
|
||||
x - half, y - half, z - half, 0f, 0f
|
||||
))
|
||||
"right" -> vertices.addAll(listOf(
|
||||
x + half, y - half, z - half, 0f, 0f,
|
||||
x + half, y - half, z + half, 1f, 0f,
|
||||
x + half, y + half, z + half, 1f, 1f,
|
||||
|
||||
x + half, y + half, z + half, 1f, 1f,
|
||||
x + half, y + half, z - half, 0f, 1f,
|
||||
x + half, y - half, z - half, 0f, 0f
|
||||
))
|
||||
"top" -> vertices.addAll(listOf(
|
||||
x - half, y + half, z - half, 0f, 0f,
|
||||
x - half, y + half, z + half, 0f, 1f,
|
||||
x + half, y + half, z + half, 1f, 1f,
|
||||
|
||||
x + half, y + half, z + half, 1f, 1f,
|
||||
x + half, y + half, z - half, 1f, 0f,
|
||||
x - half, y + half, z - half, 0f, 0f
|
||||
))
|
||||
"bottom" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z - half, 0f, 0f,
|
||||
x - half, y - half, z + half, 0f, 1f,
|
||||
x + half, y - half, z + half, 1f, 1f,
|
||||
|
||||
x + half, y - half, z + half, 1f, 1f,
|
||||
x + half, y - half, z - half, 1f, 0f,
|
||||
x - half, y - half, z - half, 0f, 0f
|
||||
))
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
fun main() {
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) throw RuntimeException("Failed to initialize GLFW")
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3)
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
|
||||
|
||||
var width = 854
|
||||
var height = 480
|
||||
val window = glfwCreateWindow(width, height, "Blocktopia", NULL, NULL)
|
||||
if (window == NULL) throw RuntimeException("Failed to create GLFW window")
|
||||
|
||||
glfwMakeContextCurrent(window)
|
||||
GL.createCapabilities()
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
|
||||
// Simple shaders
|
||||
val vertexShaderSrc = loadResource("/shaders/vertex.glsl")
|
||||
|
||||
val fragmentShaderSrc = loadResource("/shaders/fragment.glsl")
|
||||
|
||||
val shaderProgram = createShader(vertexShaderSrc, fragmentShaderSrc)
|
||||
glUseProgram(shaderProgram)
|
||||
|
||||
val modelLoc = glGetUniformLocation(shaderProgram, "model")
|
||||
val viewLoc = glGetUniformLocation(shaderProgram, "view")
|
||||
val projLoc = glGetUniformLocation(shaderProgram, "projection")
|
||||
|
||||
// Initial projection
|
||||
|
||||
// GLFW callback for resizing
|
||||
glfwSetFramebufferSizeCallback(window) { _, w, h ->
|
||||
width = w
|
||||
height = h
|
||||
updateProjection(shaderProgram, w, h)
|
||||
}
|
||||
|
||||
updateProjection(shaderProgram, width, height)
|
||||
|
||||
// Camera parameters
|
||||
|
||||
var surfaceLevel = 64
|
||||
val xSize = 16;
|
||||
val ySize = 384;
|
||||
val yOffset = 65;
|
||||
val zSize = 16;
|
||||
val cameraPos = Vector3d(0.0, surfaceLevel+2.0, 0.0)
|
||||
var pitch = 0.0
|
||||
var yaw = 0.0
|
||||
var roll = 0.0
|
||||
var startTime = glfwGetTime()
|
||||
var lastTime = startTime
|
||||
//
|
||||
//
|
||||
//
|
||||
// var lastX: Double? = null
|
||||
// var lastY: Double? = null
|
||||
//
|
||||
// val sensitivity = 5f
|
||||
//
|
||||
// var cursorDisabled = true
|
||||
//
|
||||
// var textureId = loadTexture("/resource_pack/textures/block/cobblestone.png")
|
||||
|
||||
|
||||
val vertices = mutableListOf<Float>()
|
||||
for (y in -yOffset..ySize-yOffset) {
|
||||
for (x in -xSize/2..xSize/2) {
|
||||
for (z in -zSize/2..zSize/2) {
|
||||
if (y > surfaceLevel) continue;
|
||||
// Only draw "outer" faces
|
||||
if (x == -xSize/2) addFace(vertices, x, y, z, 1f, "left")
|
||||
if (x == xSize/2) addFace(vertices, x, y, z, 1f, "right")
|
||||
if (z == -zSize/2) addFace(vertices, x, y, z, 1f, "back")
|
||||
if (z == zSize/2) addFace(vertices, x, y, z, 1f, "front")
|
||||
Game.init()
|
||||
|
||||
// Always draw top and bottom
|
||||
if (y == surfaceLevel) addFace(vertices, x, y, z, 1f, "top")
|
||||
if (y == -yOffset) addFace(vertices, x, y, z, 1f, "bottom")
|
||||
}
|
||||
Game.window?.let {
|
||||
window->
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
val currentTime = glfwGetTime()
|
||||
val deltaTime = currentTime - lastTime // in seconds
|
||||
lastTime = currentTime
|
||||
Game.render(deltaTime)
|
||||
// isOnGround = cameraPos.y <= surfaceLevel + 2
|
||||
// //yaw += 0.5f
|
||||
// //cameraPos.y += 0.25f*deltaTime
|
||||
// // Forward vector (direction camera is looking in XZ plane)
|
||||
// val forwardX = sin(yaw)
|
||||
// val forwardZ = cos(yaw)
|
||||
//
|
||||
//// Right vector (perpendicular to forward)
|
||||
// val rightX = -cos(yaw)
|
||||
// val rightZ = sin(yaw)
|
||||
// val ticksPerSecond = 20.0
|
||||
// val slipperiness = if (isOnGround) 0.6 else 1.0
|
||||
// val baseAccelerationPerTick = 0.1
|
||||
// val sprintMultiplier = if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) 1.3 else 1.0
|
||||
// val acceleration = (baseAccelerationPerTick * sprintMultiplier*(0.6/slipperiness).pow(3))*2.5
|
||||
//
|
||||
//// Apply friction
|
||||
// val frictionPerTick = 0.91 * slipperiness // combined tick friction + slipperiness
|
||||
// val friction = frictionPerTick.pow(ticksPerSecond * deltaTime)
|
||||
//
|
||||
// velocity.x *= friction
|
||||
// velocity.z *= friction
|
||||
//
|
||||
//// --- Horizontal movement ---
|
||||
// var moveX = 0.0
|
||||
// var moveZ = 0.0
|
||||
// if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) moveZ -= 1.0
|
||||
// if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) moveZ += 1.0
|
||||
// if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) moveX += 1.0
|
||||
// if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) moveX -= 1.0
|
||||
//
|
||||
// val lengthSquared = moveX * moveX + moveZ * moveZ
|
||||
// if (lengthSquared > 0) {
|
||||
// val len = sqrt(lengthSquared)
|
||||
// moveX /= len
|
||||
// moveZ /= len
|
||||
// velocity.x += (forwardX * moveZ + rightX * moveX) * acceleration * deltaTime
|
||||
// velocity.z += (forwardZ * moveZ + rightZ * moveX) * acceleration * deltaTime
|
||||
// }
|
||||
//
|
||||
// // Apply Gravity
|
||||
// // Gravity in blocks/s²
|
||||
// if (!isOnGround) {
|
||||
// if (!isFlying) {
|
||||
// val gravityPerSecond = 0.08
|
||||
// velocity.y -= gravityPerSecond * deltaTime
|
||||
// }
|
||||
// } else if (velocity.y<0) {
|
||||
// if (isFlying) isFlying = false
|
||||
// velocity.y = 0.0
|
||||
// cameraPos.y = surfaceLevel + 2.0
|
||||
// }
|
||||
// isOnGround = (cameraPos.y+velocity.y) <= surfaceLevel + 2
|
||||
//
|
||||
//// --- Vertical movement (flying) ---
|
||||
// if (isFlying) {
|
||||
// if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
// velocity.y += acceleration * deltaTime
|
||||
// }
|
||||
// if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
|
||||
// velocity.y -= acceleration * deltaTime
|
||||
// }
|
||||
// } else if (isOnGround) {
|
||||
// if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
// velocity.y = 0.42/15
|
||||
// }
|
||||
// } else {
|
||||
// if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
// //isFlying = true
|
||||
// }
|
||||
// }
|
||||
// velocity.y *= (1-(0.2*deltaTime))
|
||||
//
|
||||
//// Update position
|
||||
// cameraPos.add(velocity)
|
||||
// //yaw += 0.1f*deltaTime
|
||||
// glEnable(GL_DEPTH_TEST)
|
||||
// glClearColor(0.2f, 1f, 1f, 1f)
|
||||
// glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
//
|
||||
// // Update camera
|
||||
// val view = getCameraMatrix(cameraPos, pitch, yaw, roll)
|
||||
// val viewBuffer = FloatArray(16)
|
||||
// view.get(viewBuffer)
|
||||
// glUniformMatrix4fv(viewLoc, false, viewBuffer)
|
||||
//
|
||||
// // Draw the grid
|
||||
// val vertexCount = vertices_size / 5
|
||||
//
|
||||
// // Normal cube
|
||||
// glBindTexture(GL_TEXTURE_2D, textureId)
|
||||
// glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0)
|
||||
// glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
||||
// val model = Matrix4f().identity() // adjust position/scale if needed
|
||||
// val modelBuffer = FloatArray(16)
|
||||
// model.get(modelBuffer)
|
||||
// glUniformMatrix4fv(modelLoc, false, modelBuffer)
|
||||
//
|
||||
// glBindVertexArray(vao)
|
||||
// glDrawArrays(GL_TRIANGLES, 0, vertexCount)
|
||||
// glBindVertexArray(0)
|
||||
//
|
||||
//
|
||||
// glfwSwapBuffers(window)
|
||||
// glfwPollEvents()
|
||||
}
|
||||
}
|
||||
// Upload to VBO/VAO
|
||||
val vao = glGenVertexArrays()
|
||||
val vbo = glGenBuffers()
|
||||
glBindVertexArray(vao)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices.toFloatArray(), GL_STATIC_DRAW)
|
||||
|
||||
val stride = 5 * 4 // 5 floats * 4 bytes
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0) // position
|
||||
glEnableVertexAttribArray(0)
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 3 * 4) // tex coords
|
||||
glEnableVertexAttribArray(1)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
glBindVertexArray(0)
|
||||
|
||||
val vertices_size = vertices.size;
|
||||
vertices.clear()
|
||||
|
||||
var lastX: Double? = null
|
||||
var lastY: Double? = null
|
||||
|
||||
val sensitivity = 5f
|
||||
|
||||
var default_speed = 5f
|
||||
|
||||
var cursorDisabled = true
|
||||
|
||||
var textureId = loadTexture("/textures/default_grass.png")
|
||||
|
||||
// Initially disable the cursor
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED)
|
||||
|
||||
glfwSetKeyCallback(window) { window, key, scancode, action, mods ->
|
||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
|
||||
cursorDisabled = !cursorDisabled
|
||||
if (cursorDisabled) {
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED)
|
||||
// Optionally recenter the cursor
|
||||
glfwSetCursorPos(window, width / 2.0, height / 2.0)
|
||||
lastX = null
|
||||
lastY = null
|
||||
} else {
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glfwSetCursorPosCallback(window) { window, xpos, ypos ->
|
||||
if (cursorDisabled && lastX!=null&&lastY!=null) {
|
||||
val dx = (xpos - lastX!!) / width
|
||||
val dy = (ypos - lastY!!) / height
|
||||
yaw -= dx * sensitivity
|
||||
pitch -= dy * sensitivity
|
||||
pitch = pitch.coerceIn(-PI/2,PI/2)
|
||||
}
|
||||
lastX = xpos
|
||||
lastY = ypos
|
||||
}
|
||||
|
||||
val velocity = Vector3d(0.0,0.0,0.0)
|
||||
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
|
||||
val currentTime = glfwGetTime()
|
||||
val deltaTime = (currentTime - lastTime).toFloat() // in seconds
|
||||
lastTime = currentTime
|
||||
|
||||
//yaw += 0.5f
|
||||
//cameraPos.y += 0.25f*deltaTime
|
||||
// Forward vector (direction camera is looking in XZ plane)
|
||||
val forwardX = sin(yaw)
|
||||
val forwardZ = cos(yaw)
|
||||
|
||||
// Right vector (perpendicular to forward)
|
||||
val rightX = -cos(yaw)
|
||||
val rightZ = sin(yaw)
|
||||
var speed = 5f
|
||||
if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) {
|
||||
speed *=2
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
|
||||
cameraPos.x -= (forwardX * speed * deltaTime).toFloat()
|
||||
cameraPos.z -= (forwardZ * speed * deltaTime).toFloat()
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
|
||||
cameraPos.y -= speed*deltaTime
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
cameraPos.y += speed*deltaTime
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
|
||||
cameraPos.x += (forwardX * speed * deltaTime).toFloat()
|
||||
cameraPos.z += (forwardZ * speed * deltaTime).toFloat()
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
|
||||
cameraPos.x += (rightX * speed * deltaTime).toFloat()
|
||||
cameraPos.z += (rightZ * speed * deltaTime).toFloat()
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
|
||||
cameraPos.x -= (rightX * speed * deltaTime).toFloat()
|
||||
cameraPos.z -= (rightZ * speed * deltaTime).toFloat()
|
||||
}
|
||||
//yaw += 0.1f*deltaTime
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glClearColor(0.2f, 1f, 1f, 1f)
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
// Update camera
|
||||
val view = getCameraMatrix(cameraPos, pitch, yaw, roll)
|
||||
val viewBuffer = FloatArray(16)
|
||||
view.get(viewBuffer)
|
||||
glUniformMatrix4fv(viewLoc, false, viewBuffer)
|
||||
|
||||
// Draw the grid
|
||||
val vertexCount = vertices_size / 5
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
|
||||
glBindVertexArray(vao)
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount)
|
||||
glBindVertexArray(0)
|
||||
|
||||
// Normal cube
|
||||
glBindTexture(GL_TEXTURE_2D, textureId)
|
||||
glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
||||
val model = Matrix4f().identity() // adjust position/scale if needed
|
||||
val modelBuffer = FloatArray(16)
|
||||
model.get(modelBuffer)
|
||||
glUniformMatrix4fv(modelLoc, false, modelBuffer)
|
||||
|
||||
glBindVertexArray(vao)
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount)
|
||||
glBindVertexArray(0)
|
||||
|
||||
|
||||
glfwSwapBuffers(window)
|
||||
glfwPollEvents()
|
||||
}
|
||||
|
||||
glfwDestroyWindow(window)
|
||||
glfwTerminate()
|
||||
Game.close()
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package dev.wbell.buildtopia.app.game
|
||||
|
||||
import dev.wbell.buildtopia.app.createShader
|
||||
import dev.wbell.buildtopia.app.game.session.Session
|
||||
import dev.wbell.buildtopia.app.game.session.world.World
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection
|
||||
import dev.wbell.buildtopia.app.game.session.world.player.Player
|
||||
import dev.wbell.buildtopia.app.loadResource
|
||||
import dev.wbell.buildtopia.app.updateProjection
|
||||
import org.joml.Vector3d
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MAJOR
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MINOR
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CURSOR
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CURSOR_NORMAL
|
||||
import org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE
|
||||
import org.lwjgl.glfw.GLFW.GLFW_OPENGL_CORE_PROFILE
|
||||
import org.lwjgl.glfw.GLFW.GLFW_OPENGL_PROFILE
|
||||
import org.lwjgl.glfw.GLFW.GLFW_PRESS
|
||||
import org.lwjgl.glfw.GLFW.glfwCreateWindow
|
||||
import org.lwjgl.glfw.GLFW.glfwDestroyWindow
|
||||
import org.lwjgl.glfw.GLFW.glfwInit
|
||||
import org.lwjgl.glfw.GLFW.glfwMakeContextCurrent
|
||||
import org.lwjgl.glfw.GLFW.glfwSetCursorPos
|
||||
import org.lwjgl.glfw.GLFW.glfwSetCursorPosCallback
|
||||
import org.lwjgl.glfw.GLFW.glfwSetFramebufferSizeCallback
|
||||
import org.lwjgl.glfw.GLFW.glfwSetInputMode
|
||||
import org.lwjgl.glfw.GLFW.glfwSetKeyCallback
|
||||
import org.lwjgl.glfw.GLFW.glfwTerminate
|
||||
import org.lwjgl.glfw.GLFW.glfwWindowHint
|
||||
import org.lwjgl.opengl.GL
|
||||
import org.lwjgl.opengl.GL11.GL_DEPTH_TEST
|
||||
import org.lwjgl.opengl.GL11.glEnable
|
||||
import org.lwjgl.opengl.GL20.glGetUniformLocation
|
||||
import org.lwjgl.opengl.GL20.glUseProgram
|
||||
import org.lwjgl.system.MemoryUtil.NULL
|
||||
import org.lwjgl.opengl.GL11.GL_NEAREST
|
||||
import org.lwjgl.opengl.GL11.GL_REPEAT
|
||||
import org.lwjgl.opengl.GL11.GL_RGBA
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_2D
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T
|
||||
import org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE
|
||||
import org.lwjgl.opengl.GL11.glBindTexture
|
||||
import org.lwjgl.opengl.GL11.glGenTextures
|
||||
import org.lwjgl.opengl.GL11.glTexImage2D
|
||||
import org.lwjgl.opengl.GL11.glTexParameteri
|
||||
import org.lwjgl.stb.STBImage
|
||||
import java.nio.IntBuffer
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
fun loadTexture(path: String): Int {
|
||||
val textureId = glGenTextures()
|
||||
glBindTexture(GL_TEXTURE_2D, textureId)
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||
|
||||
|
||||
|
||||
// Load image from resources
|
||||
val stream = object {}.javaClass.getResourceAsStream(path)
|
||||
?: throw RuntimeException("Failed to load texture: $path")
|
||||
|
||||
// Read stream into a ByteBuffer
|
||||
val bytes = stream.readBytes()
|
||||
val buffer = BufferUtils.createByteBuffer(bytes.size)
|
||||
buffer.put(bytes)
|
||||
buffer.flip()
|
||||
|
||||
val widthBuffer: IntBuffer = BufferUtils.createIntBuffer(1)
|
||||
val heightBuffer: IntBuffer = BufferUtils.createIntBuffer(1)
|
||||
val channelsBuffer: IntBuffer = BufferUtils.createIntBuffer(1)
|
||||
|
||||
val image = STBImage.stbi_load_from_memory(buffer, widthBuffer, heightBuffer, channelsBuffer, 4)
|
||||
?: throw RuntimeException("Failed to load texture: ${STBImage.stbi_failure_reason()}")
|
||||
|
||||
val width = widthBuffer.get(0)
|
||||
val height = heightBuffer.get(0)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
|
||||
STBImage.stbi_image_free(image)
|
||||
|
||||
return textureId
|
||||
}
|
||||
|
||||
object Game {
|
||||
val session = Session()
|
||||
var width = 854
|
||||
var height = 480
|
||||
var cursorDisabled = true
|
||||
var window: Long? = null
|
||||
|
||||
object Cursor {
|
||||
var lastX: Double? = null
|
||||
var lastY: Double? = null
|
||||
}
|
||||
|
||||
var textureId = 0
|
||||
|
||||
var shaderProgram = 0
|
||||
var modelLoc = 0
|
||||
var viewLoc = 0
|
||||
var projLoc = 0
|
||||
|
||||
fun init() {
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) throw RuntimeException("Failed to initialize GLFW")
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3)
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
|
||||
window = glfwCreateWindow(width, height, "Blocktopia", NULL, NULL)
|
||||
if (window == NULL) throw RuntimeException("Failed to create GLFW window")
|
||||
|
||||
window?.let { window ->
|
||||
glfwMakeContextCurrent(window)
|
||||
GL.createCapabilities()
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
session.World = World(Player(Vector3d(0.0, 4.0, 0.0), 0.0,0.0), session)
|
||||
session.World!!.player.world = session.World
|
||||
|
||||
textureId = loadTexture("/resource_pack/textures/block/cobblestone.png")
|
||||
|
||||
// Simple shaders
|
||||
val vertexShaderSrc = loadResource("/shaders/vertex.glsl")
|
||||
|
||||
val fragmentShaderSrc = loadResource("/shaders/fragment.glsl")
|
||||
|
||||
shaderProgram = createShader(vertexShaderSrc, fragmentShaderSrc)
|
||||
glUseProgram(shaderProgram)
|
||||
|
||||
modelLoc = glGetUniformLocation(shaderProgram, "model")
|
||||
viewLoc = glGetUniformLocation(shaderProgram, "view")
|
||||
projLoc = glGetUniformLocation(shaderProgram, "projection")
|
||||
|
||||
// Initial projection
|
||||
|
||||
// GLFW callback for resizing
|
||||
glfwSetFramebufferSizeCallback(window) { _, w, h ->
|
||||
width = w
|
||||
height = h
|
||||
updateProjection(shaderProgram, w, h)
|
||||
}
|
||||
|
||||
updateProjection(shaderProgram, width, height)
|
||||
|
||||
// Initially disable the cursor
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED)
|
||||
|
||||
glfwSetKeyCallback(window) { window, key, scancode, action, mods ->
|
||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
|
||||
cursorDisabled = !cursorDisabled
|
||||
if (cursorDisabled) {
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED)
|
||||
// Optionally recenter the cursor
|
||||
glfwSetCursorPos(window, width / 2.0, height / 2.0)
|
||||
Cursor.lastX = null
|
||||
Cursor.lastY = null
|
||||
} else {
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glfwSetCursorPosCallback(window) { window, xpos, ypos ->
|
||||
if (cursorDisabled && Cursor.lastX != null && Cursor.lastY != null) {
|
||||
val dx = (xpos - Cursor.lastX!!) / width
|
||||
val dy = (ypos - Cursor.lastY!!) / height
|
||||
session.World?.player?.yaw -= dx * 5f
|
||||
session.World?.player?.pitch -= dy * 5f
|
||||
session.World?.player?.pitch?.let {
|
||||
session.World?.player?.pitch = it.coerceIn(-PI / 2, PI / 2)
|
||||
}
|
||||
}
|
||||
Cursor.lastX = xpos
|
||||
Cursor.lastY = ypos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
window?.let { glfwDestroyWindow(it) }
|
||||
glfwTerminate()
|
||||
}
|
||||
|
||||
fun render(deltaTime: Double) {
|
||||
session.World?.render(deltaTime)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dev.wbell.buildtopia.app.game.camera
|
||||
|
||||
import org.joml.Vector3d
|
||||
|
||||
class Camera(
|
||||
val position: Vector3d,
|
||||
var pitch: Double,
|
||||
var yaw: Double,
|
||||
var roll: Double
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package dev.wbell.buildtopia.app.game.mainMenu
|
||||
|
||||
class MainMenu {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dev.wbell.buildtopia.app.game.session
|
||||
|
||||
import dev.wbell.buildtopia.app.game.session.world.World
|
||||
|
||||
class Session {
|
||||
var World: World? = null
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package dev.wbell.buildtopia.app.game.session.world
|
||||
|
||||
import dev.wbell.buildtopia.app.game.Game
|
||||
import dev.wbell.buildtopia.app.game.Game.window
|
||||
import dev.wbell.buildtopia.app.game.camera.Camera
|
||||
import dev.wbell.buildtopia.app.game.session.Session
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.Block.Block
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.BlockAccessor
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.Chunk
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection
|
||||
import dev.wbell.buildtopia.app.game.session.world.player.Player
|
||||
import dev.wbell.buildtopia.app.game.settings.SettingKey
|
||||
import dev.wbell.buildtopia.app.game.settings.Settings
|
||||
import dev.wbell.buildtopia.app.getCameraMatrix
|
||||
import org.joml.Vector2i
|
||||
import org.joml.Vector3d
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
import org.lwjgl.opengl.GL20.*
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
fun floorDiv(a: Int, b: Int): Int {
|
||||
// rounds down (towards -∞), unlike / which rounds toward 0
|
||||
var div = a / b
|
||||
if ((a xor b) < 0 && a % b != 0) {
|
||||
div -= 1
|
||||
}
|
||||
return div
|
||||
}
|
||||
|
||||
fun floorMod(a: Int, b: Int): Int {
|
||||
// always returns a result in 0 until b-1
|
||||
val mod = a % b
|
||||
return if (mod < 0) mod + b else mod
|
||||
}
|
||||
|
||||
class World(val player: Player, val session: Session) {
|
||||
private val chunkRenderer = CoroutineScope(Dispatchers.Default)
|
||||
val chunkMeshQueue = ConcurrentLinkedQueue<() -> Unit>()
|
||||
private val physics = CoroutineScope(Dispatchers.Default)
|
||||
private val chunks = mutableMapOf<Long, Chunk>()
|
||||
private var isActive = true
|
||||
var lastTick = glfwGetTime()
|
||||
|
||||
init {
|
||||
val renderDistance = Settings.get(SettingKey.RENDERDISTANCE, 32)!!
|
||||
for (x in -renderDistance..<renderDistance) {
|
||||
for (z in -renderDistance..<renderDistance) {
|
||||
chunks[packChunkKey(x, z)] =
|
||||
Chunk(
|
||||
this,
|
||||
Vector2i(x, z),
|
||||
24,
|
||||
4,
|
||||
Array(ChunkSection.LENGTH * ChunkSection.LENGTH * ChunkSection.LENGTH * 24) {
|
||||
if (it < 34000) Block() else null
|
||||
})
|
||||
}
|
||||
}
|
||||
startPhysics()
|
||||
startChunkRenderer()
|
||||
}
|
||||
|
||||
private fun startChunkRenderer() {
|
||||
chunkRenderer.launch {
|
||||
val tickTime = 50L // ms per tick (20 TPS)
|
||||
while (isActive) {
|
||||
val centerChunk = Vector2i(
|
||||
(player.position.x / ChunkSection.LENGTH).toInt(),
|
||||
(player.position.z / ChunkSection.LENGTH).toInt()
|
||||
)
|
||||
val renderDistance = Settings.get(SettingKey.RENDERDISTANCE, 12)!!
|
||||
val chunksToUpdate = mutableListOf<Pair<Chunk, Int>>() // Pair(chunk, distance^2)
|
||||
|
||||
for (dx in -renderDistance - 1..renderDistance) {
|
||||
for (dz in -renderDistance - 1..renderDistance) {
|
||||
val chunk = getChunk(centerChunk.x + dx, centerChunk.y + dz)
|
||||
if (chunk != null && chunk.updateChunk) {
|
||||
val dist2 = dx * dx + dz * dz
|
||||
chunksToUpdate.add(chunk to dist2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by distance
|
||||
chunksToUpdate.sortBy { it.second }
|
||||
for ((chunk, _) in chunksToUpdate) {
|
||||
chunk.renderMesh() // background mesh generation
|
||||
chunkMeshQueue.add { chunk.uploadChunkMesh() } // main thread upload
|
||||
}
|
||||
delay(tickTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPhysics() {
|
||||
physics.launch {
|
||||
val tickTime = 50L // ms per tick (20 TPS)
|
||||
while (isActive) {
|
||||
tick()
|
||||
lastTick = glfwGetTime()
|
||||
delay(tickTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var camera: Camera = Camera(Vector3d(0.0, 0.0, 0.0), 0.0, 0.0, 0.0)
|
||||
|
||||
private fun packChunkKey(x: Int, z: Int): Long =
|
||||
x.toLong() and 0xFFFFFFFFL or ((z.toLong() and 0xFFFFFFFFL) shl 32)
|
||||
|
||||
fun getChunk(x: Int, z: Int): Chunk? {
|
||||
//if (chunks[packChunkKey(x, z)] == null) chunks[packChunkKey(x, z)] = Chunk(this, Vector2i(x,z), 24, 4, Array(16*16*16*24){ Block()})
|
||||
return chunks[packChunkKey(x, z)]
|
||||
}
|
||||
|
||||
fun setChunk(x: Int, z: Int, chunk: Chunk) {
|
||||
chunks[packChunkKey(x, z)]?.unload()
|
||||
chunks[packChunkKey(x, z)] = chunk
|
||||
}
|
||||
|
||||
|
||||
val blocks = object : BlockAccessor {
|
||||
override operator fun get(x: Int, y: Int, z: Int): Block? {
|
||||
return getChunk(floorDiv(x, ChunkSection.LENGTH), floorDiv(z, ChunkSection.LENGTH))?.blocks[floorMod(
|
||||
x,
|
||||
ChunkSection.LENGTH
|
||||
), y, floorMod(z, ChunkSection.LENGTH)]
|
||||
}
|
||||
|
||||
override operator fun set(x: Int, y: Int, z: Int, value: Block?) {
|
||||
getChunk(floorDiv(x, ChunkSection.LENGTH), floorDiv(z, ChunkSection.LENGTH))?.blocks[floorMod(
|
||||
x,
|
||||
ChunkSection.LENGTH
|
||||
), y, floorMod(z, ChunkSection.LENGTH)] = value
|
||||
}
|
||||
}
|
||||
|
||||
fun clean() {
|
||||
|
||||
}
|
||||
|
||||
fun render(deltaTime: Double) {
|
||||
while (chunkMeshQueue.isNotEmpty()) {
|
||||
chunkMeshQueue.poll()?.invoke()
|
||||
}
|
||||
val deltaTickTime = ((glfwGetTime() - lastTick) / 0.05).coerceIn(0.0, 1.0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glClearColor(0f, 0.6f, 1f, 1f)
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
val forwardX = sin(player.yaw)
|
||||
val forwardZ = cos(player.yaw)
|
||||
// if (glfwGetKey(Game.window!!, GLFW_KEY_W) == GLFW_PRESS) {
|
||||
// player.position.x-=forwardX*deltaTime*10
|
||||
// player.position.z-=forwardZ*deltaTime*10
|
||||
// }
|
||||
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
|
||||
player.position.y -= deltaTime * 10
|
||||
}
|
||||
|
||||
camera = player.getCamera(deltaTickTime)
|
||||
|
||||
val view = getCameraMatrix(camera.position, camera.pitch, camera.yaw, camera.roll)
|
||||
val viewBuffer = FloatArray(16)
|
||||
view.get(viewBuffer)
|
||||
glUniformMatrix4fv(Game.viewLoc, false, viewBuffer)
|
||||
|
||||
val centerChunk = Vector2i(
|
||||
(player.position.x / ChunkSection.LENGTH).toInt(),
|
||||
(player.position.z / ChunkSection.LENGTH).toInt()
|
||||
)
|
||||
val renderDistance = Settings.get(SettingKey.RENDERDISTANCE, 12)!!
|
||||
for (dx in -renderDistance - 1..renderDistance) {
|
||||
for (dz in -renderDistance - 1..renderDistance) {
|
||||
val chunk = getChunk(centerChunk.x + dx, centerChunk.y + dz)
|
||||
chunk?.render(deltaTickTime)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
window?.let { window ->
|
||||
glfwSwapBuffers(window)
|
||||
glfwPollEvents()
|
||||
}
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
player.tick()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package dev.wbell.buildtopia.app.game.session.world.chunk.Block
|
||||
|
||||
|
||||
// @formatter:off
|
||||
fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, cubeScale: Float, face: String) {
|
||||
val half = cubeScale / 2f
|
||||
when(face) {
|
||||
"front" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z + half, 0f, 1f, // bottom-left
|
||||
x + half, y - half, z + half, 1f, 1f, // bottom-right
|
||||
x + half, y + half, z + half, 1f, 0f, // top-right
|
||||
|
||||
x + half, y + half, z + half, 1f, 0f,
|
||||
x - half, y + half, z + half, 0f, 0f, // top-left
|
||||
x - half, y - half, z + half, 0f, 1f
|
||||
))
|
||||
"back" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z - half, 1f, 1f,
|
||||
x + half, y - half, z - half, 0f, 1f,
|
||||
x + half, y + half, z - half, 0f, 0f,
|
||||
|
||||
x + half, y + half, z - half, 0f, 0f,
|
||||
x - half, y + half, z - half, 1f, 0f,
|
||||
x - half, y - half, z - half, 1f, 1f
|
||||
))
|
||||
"left" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z - half, 0f, 1f,
|
||||
x - half, y - half, z + half, 1f, 1f,
|
||||
x - half, y + half, z + half, 1f, 0f,
|
||||
|
||||
x - half, y + half, z + half, 1f, 0f,
|
||||
x - half, y + half, z - half, 0f, 0f,
|
||||
x - half, y - half, z - half, 0f, 1f
|
||||
))
|
||||
"right" -> vertices.addAll(listOf(
|
||||
x + half, y - half, z - half, 1f, 1f,
|
||||
x + half, y - half, z + half, 0f, 1f,
|
||||
x + half, y + half, z + half, 0f, 0f,
|
||||
|
||||
x + half, y + half, z + half, 0f, 0f,
|
||||
x + half, y + half, z - half, 1f, 0f,
|
||||
x + half, y - half, z - half, 1f, 1f
|
||||
))
|
||||
"top" -> vertices.addAll(listOf(
|
||||
x - half, y + half, z - half, 0f, 0f,
|
||||
x - half, y + half, z + half, 0f, 1f,
|
||||
x + half, y + half, z + half, 1f, 1f,
|
||||
|
||||
x + half, y + half, z + half, 1f, 1f,
|
||||
x + half, y + half, z - half, 1f, 0f,
|
||||
x - half, y + half, z - half, 0f, 0f
|
||||
))
|
||||
"bottom" -> vertices.addAll(listOf(
|
||||
x - half, y - half, z - half, 1f, 0f,
|
||||
x - half, y - half, z + half, 1f, 1f,
|
||||
x + half, y - half, z + half, 0f, 1f,
|
||||
|
||||
x + half, y - half, z + half, 0f, 1f,
|
||||
x + half, y - half, z - half, 0f, 0f,
|
||||
x - half, y - half, z - half, 1f, 0f
|
||||
))
|
||||
}
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
class Block {
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package dev.wbell.buildtopia.app.game.session.world.chunk
|
||||
|
||||
import dev.wbell.buildtopia.app.game.Game
|
||||
import dev.wbell.buildtopia.app.game.session.world.World
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.Block.Block
|
||||
import dev.wbell.buildtopia.app.game.session.world.chunk.Block.addFace
|
||||
import dev.wbell.buildtopia.app.game.session.world.floorDiv
|
||||
import dev.wbell.buildtopia.app.game.session.world.floorMod
|
||||
import jogamp.common.os.elf.Section
|
||||
import org.joml.Matrix4d
|
||||
import org.joml.Matrix4f
|
||||
import org.joml.Vector2d
|
||||
import org.joml.Vector2i
|
||||
import org.joml.Vector3d
|
||||
import org.joml.Vector3f
|
||||
import org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray
|
||||
import org.lwjgl.opengl.ARBVertexArrayObject.glGenVertexArrays
|
||||
import org.lwjgl.opengl.GL11.GL_FLOAT
|
||||
import org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER
|
||||
import org.lwjgl.opengl.GL15.GL_STATIC_DRAW
|
||||
import org.lwjgl.opengl.GL15.glBindBuffer
|
||||
import org.lwjgl.opengl.GL15.glBufferData
|
||||
import org.lwjgl.opengl.GL15.glGenBuffers
|
||||
import org.lwjgl.opengl.GL20.glEnableVertexAttribArray
|
||||
import org.lwjgl.opengl.GL20.glVertexAttribPointer
|
||||
import org.lwjgl.opengl.GL30.*
|
||||
|
||||
|
||||
interface BlockAccessor {
|
||||
operator fun get(x: Int, y: Int, z: Int): Block?
|
||||
operator fun set(x: Int, y: Int, z: Int, value: Block?)
|
||||
}
|
||||
|
||||
class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: Int) {
|
||||
companion object {
|
||||
const val LENGTH = 16
|
||||
const val SIZE = LENGTH * LENGTH * LENGTH
|
||||
}
|
||||
|
||||
val blocks = object : BlockAccessor {
|
||||
override operator fun get(x: Int, y: Int, z: Int): Block? {
|
||||
val index = x + z * LENGTH + y * LENGTH * LENGTH
|
||||
if ((x !in 0..<LENGTH) || (z !in 0..<LENGTH) || (y !in 0..<LENGTH)) return null
|
||||
return blockData[index]
|
||||
}
|
||||
|
||||
override operator fun set(x: Int, y: Int, z: Int, value: Block?) {
|
||||
val index = x + z * LENGTH + y * LENGTH * LENGTH
|
||||
if (index < 0 || index >= blockData.size) return
|
||||
blockData[index] = value
|
||||
if (x == 0) chunk.world.getChunk(chunk.coords.x - 1, chunk.coords.y)?.updateChunk = true
|
||||
if (x == LENGTH - 1) chunk.world.getChunk(chunk.coords.x + 1, chunk.coords.y)?.updateChunk = true
|
||||
if (z == 0) chunk.world.getChunk(chunk.coords.x, chunk.coords.y - 1)?.updateChunk = true
|
||||
if (z == LENGTH - 1) chunk.world.getChunk(chunk.coords.x, chunk.coords.y + 1)?.updateChunk = true
|
||||
}
|
||||
}
|
||||
|
||||
fun setVertices(vertices: MutableList<Float>) {
|
||||
for (x in 0..<LENGTH) {
|
||||
for (sectionY in 0..<LENGTH) {
|
||||
val y = sectionY + LENGTH * index - chunk.ySectionsOffset * LENGTH
|
||||
for (z in 0..<LENGTH) {
|
||||
if (blocks[x, sectionY, z] == null) continue;
|
||||
val worldX = x + chunk.coords.x * LENGTH
|
||||
val worldZ = z + chunk.coords.y * LENGTH
|
||||
if (chunk.world.blocks[worldX, y + 1, worldZ] == null) addFace(vertices, x, y, z, 1f, "top")
|
||||
if (chunk.world.blocks[worldX, y - 1, worldZ] == null) addFace(vertices, x, y, z, 1f, "bottom")
|
||||
if (chunk.world.blocks[worldX + 1, y, worldZ] == null) addFace(vertices, x, y, z, 1f, "right")
|
||||
if (chunk.world.blocks[worldX - 1, y, worldZ] == null) addFace(vertices, x, y, z, 1f, "left")
|
||||
if (chunk.world.blocks[worldX, y, worldZ + 1] == null) addFace(vertices, x, y, z, 1f, "front")
|
||||
if (chunk.world.blocks[worldX, y, worldZ - 1] == null) addFace(vertices, x, y, z, 1f, "back")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Chunk(
|
||||
val world: World,
|
||||
val coords: Vector2i,
|
||||
val numberOfSections: Int,
|
||||
val ySectionsOffset: Int,
|
||||
val blockData: Array<Block?>
|
||||
) {
|
||||
private val sections: Array<ChunkSection> = Array(numberOfSections) { sectionIndex ->
|
||||
ChunkSection(Array(ChunkSection.SIZE) { i ->
|
||||
val x = i % ChunkSection.LENGTH
|
||||
val z = (i / ChunkSection.LENGTH) % ChunkSection.LENGTH
|
||||
val y = (i / (ChunkSection.LENGTH * ChunkSection.LENGTH)) + sectionIndex * ChunkSection.LENGTH
|
||||
blockData[x + z * ChunkSection.LENGTH + y * ChunkSection.LENGTH * ChunkSection.LENGTH]
|
||||
}, this, sectionIndex)
|
||||
}
|
||||
val blocks = object : BlockAccessor {
|
||||
override operator fun get(x: Int, y: Int, z: Int): Block? {
|
||||
val sectionIndex = floorDiv((y + ySectionsOffset * ChunkSection.LENGTH), ChunkSection.LENGTH)
|
||||
if (sectionIndex !in 0..<numberOfSections) return null
|
||||
return sections[sectionIndex].blocks[x, floorMod(y, ChunkSection.LENGTH), z]
|
||||
}
|
||||
|
||||
override operator fun set(x: Int, y: Int, z: Int, value: Block?) {
|
||||
val sectionIndex = floorDiv((y - ySectionsOffset * ChunkSection.LENGTH), ChunkSection.LENGTH)
|
||||
if (sectionIndex !in 0..<numberOfSections) return
|
||||
sections[sectionIndex].blocks[x, floorMod(y, ChunkSection.LENGTH), z] = value
|
||||
}
|
||||
}
|
||||
|
||||
private var meshExists: Boolean = false
|
||||
var vao: Int = 0
|
||||
var vaoSize: Int = 0
|
||||
var vbo: Int = 0
|
||||
var updateChunk: Boolean = true
|
||||
var verticesForUpload:MutableList<Float>? = null
|
||||
|
||||
|
||||
fun renderMesh() {
|
||||
verticesForUpload = mutableListOf<Float>()
|
||||
for (i in 0..<numberOfSections) {
|
||||
sections[i].setVertices(verticesForUpload!!)
|
||||
}
|
||||
updateChunk = false
|
||||
// if (meshExists) {
|
||||
// glDeleteBuffers(vbo)
|
||||
// glDeleteVertexArrays(vao)
|
||||
// }
|
||||
// vao = glGenVertexArrays()
|
||||
// vbo = glGenBuffers()
|
||||
// glBindVertexArray(vao)
|
||||
// glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
||||
// glBufferData(GL_ARRAY_BUFFER, vertices.toFloatArray(), GL_STATIC_DRAW)
|
||||
//
|
||||
// val stride = 5 * 4 // 5 floats * 4 bytes
|
||||
// glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0) // position
|
||||
// glEnableVertexAttribArray(0)
|
||||
// glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 3 * 4) // tex coords
|
||||
// glEnableVertexAttribArray(1)
|
||||
//
|
||||
// glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
// glBindVertexArray(0)
|
||||
//
|
||||
// vaoSize = vertices.size;
|
||||
// meshExists = true
|
||||
}
|
||||
|
||||
fun uploadChunkMesh() {
|
||||
if (meshExists) {
|
||||
glDeleteBuffers(vbo)
|
||||
glDeleteVertexArrays(vao)
|
||||
}
|
||||
this.vao = glGenVertexArrays()
|
||||
this.vbo = glGenBuffers()
|
||||
glBindVertexArray(vao)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
||||
glBufferData(GL_ARRAY_BUFFER, verticesForUpload!!.toFloatArray(), GL_STATIC_DRAW)
|
||||
|
||||
val stride = 5 * 4 // 5 floats * 4 bytes
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0) // position
|
||||
glEnableVertexAttribArray(0)
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 3 * 4) // tex coords
|
||||
glEnableVertexAttribArray(1)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
glBindVertexArray(0)
|
||||
|
||||
this.vaoSize = verticesForUpload!!.size;
|
||||
meshExists = true
|
||||
verticesForUpload = null
|
||||
}
|
||||
|
||||
|
||||
fun render(deltaTime: Double) {
|
||||
|
||||
// Upload to VBO/VAO
|
||||
|
||||
|
||||
if (meshExists) {
|
||||
// Draw the grid
|
||||
val vertexCount = vaoSize / 5
|
||||
|
||||
// Normal cube
|
||||
glBindTexture(GL_TEXTURE_2D, Game.textureId)
|
||||
glUniform1i(glGetUniformLocation(Game.shaderProgram, "texture1"), 0)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
||||
val model = Matrix4f().identity().translate(
|
||||
Vector3f(
|
||||
(coords.x * ChunkSection.LENGTH).toFloat(),
|
||||
-(ChunkSection.LENGTH * ySectionsOffset).toFloat(),
|
||||
(coords.y * ChunkSection.LENGTH).toFloat()
|
||||
)
|
||||
) // adjust position/scale if needed
|
||||
val modelBuffer = FloatArray(16)
|
||||
model.get(modelBuffer)
|
||||
glUniformMatrix4fv(Game.modelLoc, false, modelBuffer)
|
||||
|
||||
glBindVertexArray(vao)
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount)
|
||||
glBindVertexArray(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun unload() {}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package dev.wbell.buildtopia.app.game.session.world.player
|
||||
|
||||
import dev.wbell.buildtopia.app.game.Game
|
||||
import dev.wbell.buildtopia.app.game.camera.Camera
|
||||
import dev.wbell.buildtopia.app.game.session.world.World
|
||||
import org.joml.Vector2d
|
||||
import org.joml.Vector3d
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
import org.lwjgl.glfw.GLFW.glfwGetKey
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
|
||||
class Player(val position: Vector3d, var pitch: Double, var yaw: Double) {
|
||||
var world: World? = null
|
||||
val velocity = Vector3d(0.0, 0.0, 0.0)
|
||||
|
||||
private var isOnGround = false
|
||||
|
||||
// Store last and current positions for interpolation
|
||||
private val lastPosition = Vector3d(position)
|
||||
|
||||
fun tick() {
|
||||
lastPosition.set(position)
|
||||
val blockUnderneath = world!!.blocks[position.x.toInt(),position.y.toInt()-2,position.z.toInt()]
|
||||
isOnGround = position.y + velocity.y <= 4
|
||||
|
||||
velocity.y -= 0.08
|
||||
if (position.y + velocity.y <= 4) {
|
||||
position.y = 4.0
|
||||
velocity.y = 0.0
|
||||
}
|
||||
if (isOnGround&&glfwGetKey(Game.window!!, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
velocity.y = 0.42
|
||||
}
|
||||
val slipperiness = if (isOnGround) 0.6 else 1.0
|
||||
val forward = Vector2d(-sin(yaw), -cos(yaw))
|
||||
val left = Vector2d(-cos(yaw), sin(yaw))
|
||||
val movement = Vector2d(0.0,0.0)
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_W) == GLFW_PRESS) {
|
||||
movement.x=1.0
|
||||
}
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_S) == GLFW_PRESS) {
|
||||
movement.x=-1.0
|
||||
}
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_A) == GLFW_PRESS) {
|
||||
movement.y=1.0
|
||||
}
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_D) == GLFW_PRESS) {
|
||||
movement.y=-1.0
|
||||
}
|
||||
if (movement.lengthSquared() != 0.0) {
|
||||
movement.normalize()
|
||||
}
|
||||
movement.mul(0.1)
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) {
|
||||
movement.mul(1.3)
|
||||
}
|
||||
velocity.x = velocity.x*slipperiness*0.91+movement.x*(0.6/slipperiness).pow(3)*forward.x+movement.y*(0.6/slipperiness).pow(3)*left.x
|
||||
velocity.z = velocity.z*slipperiness*0.91+movement.x*(0.6/slipperiness).pow(3)*forward.y+movement.y*(0.6/slipperiness).pow(3)*left.y
|
||||
position.add(velocity)
|
||||
}
|
||||
|
||||
fun getCamera(alpha: Double): Camera {
|
||||
val interpPos = Vector3d(
|
||||
lerp(lastPosition.x, position.x, alpha),
|
||||
lerp(lastPosition.y, position.y, alpha),
|
||||
lerp(lastPosition.z, position.z, alpha)
|
||||
)
|
||||
return Camera(interpPos.add(0.0, 1.0, 0.0), pitch, yaw, 0.0)
|
||||
}
|
||||
|
||||
private fun lerp(a: Double, b: Double, t: Double): Double {
|
||||
return a + (b - a) * t
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.wbell.buildtopia.app.game.settings
|
||||
|
||||
// Define keys + their types
|
||||
// Each setting key has its own type parameter
|
||||
sealed class SettingKey<T>(val key: String) {
|
||||
object FOV : SettingKey<Int>("fov")
|
||||
object RENDERDISTANCE : SettingKey<Int>("render_distance")
|
||||
}
|
||||
|
||||
object Settings {
|
||||
private val data = mutableMapOf<SettingKey<*>, Any>()
|
||||
|
||||
fun <T : Any> set(key: SettingKey<T>, value: T) {
|
||||
data[key] = value
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> get(key: SettingKey<T>, defaultValue: T? = null): T? {
|
||||
val value = data[key]
|
||||
return if (value != null) value as T else defaultValue
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 276 B |
Reference in new issue
Block a user