add texture atlas and start setting up server system

This commit is contained in:
William Bell committed 2025-10-15 17:14:25 +01:00
1 parent dba91b8476
commit 71457794be
18 files changed
+448 -163

No files matched your search

+1
View File
@@ -22,6 +22,7 @@
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/buildSrc" /> <option value="$PROJECT_DIR$/buildSrc" />
<option value="$PROJECT_DIR$/lib/mc_terrain_java" />
<option value="$PROJECT_DIR$/utils" /> <option value="$PROJECT_DIR$/utils" />
</set> </set>
</option> </option>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <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_17" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
</project> </project>
Generated
+1
View File
@@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/lib/mc_terrain_java" vcs="Git" />
</component> </component>
</project> </project>
+36 -5
View File
@@ -4,14 +4,27 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3ee4ea58-b8cf-44ca-b7e6-78580145f11f" name="Changes" comment="add resource pack"> <list default="true" id="3ee4ea58-b8cf-44ca-b7e6-78580145f11f" name="Changes" comment="change renderer to be split up into renderering new chunks and re rendering old ones for speed">
<change afterPath="$PROJECT_DIR$/app/src/main/kotlin/Resource_Pack_Loader/TextureAtlas/ClasspathResourceProvider/ClasspathResourceProvider.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/kotlin/Resource_Pack_Loader/TextureAtlas/FileSystemProvider/FileResourceProvider.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/kotlin/Resource_Pack_Loader/TextureAtlas/TextureAtlas.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/Server/InternalServer.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/Server/Server.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/gradle.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/gradle.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/app/build.gradle.kts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/kotlin/App.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/App.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/Game.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/Game.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/Game.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/Game.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/World.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/World.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/World.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/World.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/chunk/Block/Block.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/chunk/Block/Block.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/chunk/Chunk.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/chunk/Chunk.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/chunk/Chunk.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/chunk/Chunk.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/player/Player.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/player/Player.kt" afterDir="false" /> <change beforePath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/player/Player.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/kotlin/game/session/world/player/Player.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/resources/assets/Blocktopia/shaders/core/terrain.fsh" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/resources/assets/Blocktopia/shaders/core/terrain.fsh" afterDir="false" /> <change beforePath="$PROJECT_DIR$/app/src/main/resources/assets/Blocktopia/shaders/core/terrain.fsh" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/resources/assets/Blocktopia/shaders/core/terrain.fsh" afterDir="false" />
<change beforePath="$PROJECT_DIR$/lib/mc_terrain_java/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/lib/mc_terrain_java/build.gradle" afterDir="false" />
<change beforePath="$PROJECT_DIR$/lib/mc_terrain_java/src/main/java/com/seedfinding/mcterrain/terrain/SurfaceGenerator.java" beforeDir="false" afterPath="$PROJECT_DIR$/lib/mc_terrain_java/src/main/java/com/seedfinding/mcterrain/terrain/SurfaceGenerator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/settings.gradle.kts" beforeDir="false" afterPath="$PROJECT_DIR$/settings.gradle.kts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -30,8 +43,14 @@
<activation /> <activation />
</task> </task>
<projects_view> <projects_view>
<option name="groupModules" value="false" />
<tree_state> <tree_state>
<expand /> <expand>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Buildtopia" type="f1a62948:ProjectNode" />
</path>
</expand>
<select /> <select />
</tree_state> </tree_state>
</projects_view> </projects_view>
@@ -42,6 +61,8 @@
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="Class" /> <option value="Class" />
<option value="Kotlin Interface" />
<option value="Kotlin Class" />
</list> </list>
</option> </option>
</component> </component>
@@ -81,7 +102,8 @@
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, &quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;, &quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot; &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/william/Documents/GitHub/mc_terrain_java&quot;
} }
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
@@ -167,7 +189,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1759638464558</updated> <updated>1759638464558</updated>
</task> </task>
<option name="localTasksCounter" value="6" /> <task id="LOCAL-00006" summary="change renderer to be split up into renderering new chunks and re rendering old ones for speed">
<option name="closed" value="true" />
<created>1759731329848</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1759731329848</updated>
</task>
<option name="localTasksCounter" value="7" />
<servers /> <servers />
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
@@ -175,6 +205,7 @@
<MESSAGE value="add render for the specific session instead of directly calling world" /> <MESSAGE value="add render for the specific session instead of directly calling world" />
<MESSAGE value="add noise based terrain, with caves, and better mesh render times." /> <MESSAGE value="add noise based terrain, with caves, and better mesh render times." />
<MESSAGE value="add resource pack" /> <MESSAGE value="add resource pack" />
<option name="LAST_COMMIT_MESSAGE" value="add resource pack" /> <MESSAGE value="change renderer to be split up into renderering new chunks and re rendering old ones for speed" />
<option name="LAST_COMMIT_MESSAGE" value="change renderer to be split up into renderering new chunks and re rendering old ones for speed" />
</component> </component>
</project> </project>
+12
View File
@@ -9,6 +9,11 @@ plugins {
} }
val lwjglVersion = "3.3.6" val lwjglVersion = "3.3.6"
repositories {
mavenCentral()
maven(url = "https://maven.seedfinding.com") // ✅ use url= keyword
}
dependencies { dependencies {
implementation(project(":utils")) implementation(project(":utils"))
@@ -22,6 +27,13 @@ dependencies {
implementation("org.lwjgl:lwjgl-glfw:$lwjglVersion") implementation("org.lwjgl:lwjgl-glfw:$lwjglVersion")
implementation("org.lwjgl:lwjgl-stb:$lwjglVersion") implementation("org.lwjgl:lwjgl-stb:$lwjglVersion")
implementation(project(":mc_terrain_java"))
implementation("com.seedfinding:mc_math:1.171.0")
implementation("com.seedfinding:mc_seed:1.171.1")
implementation("com.seedfinding:mc_core:1.192.1")
implementation("com.seedfinding:mc_noise:1.171.1")
implementation("com.seedfinding:mc_biome:1.171.1")
// Natives for all platforms // Natives for all platforms
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:natives-linux") runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:natives-linux")
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:natives-windows") runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:natives-windows")
+1 -3
View File
@@ -1,9 +1,7 @@
package dev.wbell.buildtopia.app package dev.wbell.buildtopia.app
import dev.wbell.buildtopia.app.game.Game import dev.wbell.buildtopia.app.game.Game
import org.joml.Matrix4f
import org.lwjgl.glfw.GLFW.* import org.lwjgl.glfw.GLFW.*
import org.lwjgl.opengl.GL11.*
import org.lwjgl.opengl.GL20.* import org.lwjgl.opengl.GL20.*
fun loadResource(path: String): String { fun loadResource(path: String): String {
@@ -35,7 +33,7 @@ fun main() {
// Camera parameters // Camera parameters
var startTime = glfwGetTime() val startTime = glfwGetTime()
var lastTime = startTime var lastTime = startTime
// //
// //
@@ -0,0 +1,49 @@
package dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.ClasspathResourceProvider
import dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.ResourceProvider
import java.awt.image.BufferedImage
import java.io.File
import java.io.InputStream
import java.util.jar.JarFile
import javax.imageio.ImageIO
class ClasspathResourceProvider(
private val basePath: String, // e.g. "/assets/Blocktopia/textures"
private val resourceOwner: Any // usually `this`
) : ResourceProvider {
override fun listPngFiles(): List<String> {
val uri = resourceOwner.javaClass.getResource(basePath)?.toURI() ?: return emptyList()
return when {
uri.scheme == "file" -> { // Running unpacked (IDE/dev mode)
println(File(uri).walkTopDown()
.filter { it.extension == "png" }
.map { it.relativeTo(File(uri)).path.replace("\\", "/") }
.toList())
File(uri).walkTopDown()
.filter { it.extension == "png" }
.map { it.relativeTo(File(uri)).path.replace("\\", "/") }
.toList()
}
uri.scheme == "jar" -> { // Running from packaged .jar
val jarPath = uri.schemeSpecificPart.substringBefore("!")
.removePrefix("file:")
val jar = JarFile(jarPath)
val entries = jar.entries().toList()
val prefix = basePath.removePrefix("/").trimEnd('/') + "/"
entries
.filter { it.name.startsWith(prefix) && it.name.endsWith(".png") }
.map { it.name.removePrefix(prefix) }
}
else -> emptyList()
}
}
override fun loadImage(path: String): BufferedImage? {
val stream: InputStream? = resourceOwner.javaClass.getResourceAsStream("$basePath/$path")
return stream?.use { ImageIO.read(it) }
}
}
@@ -0,0 +1,19 @@
package dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.FileSystemProvider
import dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.ResourceProvider
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
class FileResourceProvider(private val rootPath: String) : ResourceProvider {
override fun listPngFiles(): List<String> =
File(rootPath).walkTopDown()
.filter { it.extension == "png" }
.map { it.relativeTo(File(rootPath)).path.replace("\\", "/") }
.toList()
override fun loadImage(path: String): BufferedImage? {
val file = File("$rootPath/$path")
return if (file.exists()) ImageIO.read(file) else null
}
}
@@ -0,0 +1,133 @@
package dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas
import org.joml.Vector2f
import org.lwjgl.BufferUtils
import java.awt.image.BufferedImage
import kotlin.math.ceil
import kotlin.math.sqrt
import org.lwjgl.opengl.GL11.*
import java.awt.Image
interface ResourceProvider {
fun listPngFiles(): List<String> // e.g. "block/grass_block.png"
fun loadImage(path: String): BufferedImage?
}
class TextureAtlas(
private val resources: ResourceProvider,
private val tileSize: Int = 16
) {
data class UV(val topLeft: Vector2f, val bottomRight: Vector2f)
val textureMap = mutableMapOf<String, UV>()
var textureId = 0
private set
private var atlasWidth = 0
private var atlasHeight = 0
private val missingTexture = createMissingTexture()
fun buildAtlas() {
val files = resources.listPngFiles()
val count = files.size
val padding = 1 // 1 pixel padding around each tile
val paddedTileSize = tileSize + 2 * padding
val gridSize = ceil(sqrt(count.toDouble() + 1)).toInt()
atlasWidth = gridSize * paddedTileSize
atlasHeight = gridSize * paddedTileSize
val atlasImage = BufferedImage(atlasWidth, atlasHeight, BufferedImage.TYPE_INT_ARGB)
val g = atlasImage.createGraphics()
var index = 0
for (path in files) {
val image = resources.loadImage(path) ?: missingTexture
// Scale to tile size
val scaled = image.getScaledInstance(tileSize, tileSize, Image.SCALE_SMOOTH)
val finalImage = BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB)
val g2 = finalImage.createGraphics()
g2.drawImage(scaled, 0, 0, null)
g2.dispose()
val gridX = index % gridSize
val gridY = index / gridSize
val x = gridX * paddedTileSize
val y = gridY * paddedTileSize
// Draw the main tile in the center of the padded area
g.drawImage(finalImage, x + padding, y + padding, null)
// Duplicate edges for padding (top, bottom, left, right)
// left/right columns
g.drawImage(finalImage.getSubimage(0, 0, 1, tileSize), x, y + padding, null) // left
g.drawImage(finalImage.getSubimage(tileSize - 1, 0, 1, tileSize), x + padding + tileSize, y + padding, null) // right
// top/bottom rows
g.drawImage(finalImage.getSubimage(0, 0, tileSize, 1), x + padding, y, null) // top
g.drawImage(finalImage.getSubimage(0, tileSize - 1, tileSize, 1), x + padding, y + padding + tileSize, null) // bottom
// corners
g.drawImage(finalImage.getSubimage(0, 0, 1, 1), x, y, null) // top-left
g.drawImage(finalImage.getSubimage(tileSize - 1, 0, 1, 1), x + padding + tileSize, y, null) // top-right
g.drawImage(finalImage.getSubimage(0, tileSize - 1, 1, 1), x, y + padding + tileSize, null) // bottom-left
g.drawImage(finalImage.getSubimage(tileSize - 1, tileSize - 1, 1, 1), x + padding + tileSize, y + padding + tileSize, null) // bottom-right
// Compute UVs only for inner tile area
val u1 = (x + padding).toFloat() / atlasWidth
val v1 = (y + padding).toFloat() / atlasHeight
val u2 = (x + padding + tileSize).toFloat() / atlasWidth
val v2 = (y + padding + tileSize).toFloat() / atlasHeight
textureMap[path] = UV(Vector2f(u1, v1), Vector2f(u2, v2))
index++
}
// Add missing texture the same way
val gridX = index % gridSize
val gridY = index / gridSize
val x = gridX * paddedTileSize
val y = gridY * paddedTileSize
g.drawImage(missingTexture, x + padding, y + padding, null)
val u1 = (x + padding).toFloat() / atlasWidth
val v1 = (y + padding).toFloat() / atlasHeight
val u2 = (x + padding + tileSize).toFloat() / atlasWidth
val v2 = (y + padding + tileSize).toFloat() / atlasHeight
textureMap["missing"] = UV(Vector2f(u1, v1), Vector2f(u2, v2))
g.dispose()
uploadToGL(atlasImage)
}
private fun createMissingTexture(): BufferedImage {
val img = BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB)
val magenta = 0xFFFF00FF.toInt()
val black = 0xFF000000.toInt()
for (y in 0 until tileSize)
for (x in 0 until tileSize)
img.setRGB(x, y, if (((x / (tileSize / 2)) + (y / (tileSize / 2))) % 2 == 0) magenta else black)
return img
}
private fun uploadToGL(image: BufferedImage) {
val pixels = IntArray(image.width * image.height)
image.getRGB(0, 0, image.width, image.height, pixels, 0, image.width)
val buffer = BufferUtils.createByteBuffer(image.width * image.height * 4)
for (y in 0 until image.height) {
for (x in 0 until image.width) {
val p = pixels[y * image.width + x]
buffer.put(((p shr 16) and 0xFF).toByte())
buffer.put(((p shr 8) and 0xFF).toByte())
buffer.put((p and 0xFF).toByte())
buffer.put(((p shr 24) and 0xFF).toByte())
}
}
buffer.flip()
textureId = glGenTextures()
glBindTexture(GL_TEXTURE_2D, textureId)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glBindTexture(GL_TEXTURE_2D, 0)
}
fun getUV(path: String): UV = textureMap[path] ?: textureMap["missing"]!!//UV(Vector2f(0f,0f), Vector2f(1f,1f))//
}
+18 -43
View File
@@ -1,13 +1,16 @@
package dev.wbell.buildtopia.app.game package dev.wbell.buildtopia.app.game
import dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.ClasspathResourceProvider.ClasspathResourceProvider
import dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.TextureAtlas
import dev.wbell.buildtopia.app.createShader import dev.wbell.buildtopia.app.createShader
import dev.wbell.buildtopia.app.game.session.Session 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.World
import dev.wbell.buildtopia.app.game.session.world.chunk.Block.BlockRegistry
import dev.wbell.buildtopia.app.game.session.world.chunk.Block.BlockType
import dev.wbell.buildtopia.app.game.session.world.player.Player import dev.wbell.buildtopia.app.game.session.world.player.Player
import dev.wbell.buildtopia.app.loadResource import dev.wbell.buildtopia.app.loadResource
import org.joml.Matrix4f import org.joml.Matrix4f
import org.joml.Vector3d import org.joml.Vector3d
import org.lwjgl.BufferUtils
import org.lwjgl.glfw.GLFW.* import org.lwjgl.glfw.GLFW.*
import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL20.glGetUniformLocation import org.lwjgl.opengl.GL20.glGetUniformLocation
@@ -15,46 +18,8 @@ import org.lwjgl.opengl.GL20.glUseProgram
import org.lwjgl.system.MemoryUtil.NULL import org.lwjgl.system.MemoryUtil.NULL
import org.lwjgl.opengl.GL13.* import org.lwjgl.opengl.GL13.*
import org.lwjgl.opengl.GL20.glUniformMatrix4fv import org.lwjgl.opengl.GL20.glUniformMatrix4fv
import org.lwjgl.stb.STBImage
import java.nio.IntBuffer
import kotlin.math.PI 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 { object Game {
val session = Session() val session = Session()
var width = 854 var width = 854
@@ -67,7 +32,7 @@ object Game {
var lastY: Double? = null var lastY: Double? = null
} }
var textureId = 0 val block_atlas = TextureAtlas(ClasspathResourceProvider("/assets/Blocktopia/textures/block", this))
var shaderProgram = 0 var shaderProgram = 0
var modelLoc = 0 var modelLoc = 0
@@ -91,7 +56,12 @@ object Game {
} }
fun init() { fun init() {
// Initialize GLFW // Initialize GLF
BlockRegistry.register(BlockType("minecraft:air", "Air", "", transparent = true, solid = false))
println(BlockRegistry.getIndex("minecraft:air"))
BlockRegistry.register(BlockType("minecraft:stone", "Stone", "block/stone.png"))
BlockRegistry.register(BlockType("minecraft:dirt", "Dirt", "block/dirt.png", transparent = true))
BlockRegistry.register(BlockType("minecraft:glass_block", "Glass Block", "block/grass_block.png", transparent = true))
if (!glfwInit()) throw RuntimeException("Failed to initialize GLFW") if (!glfwInit()) throw RuntimeException("Failed to initialize GLFW")
glfwWindowHint(GLFW_SAMPLES, 4) glfwWindowHint(GLFW_SAMPLES, 4)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
@@ -109,8 +79,13 @@ object Game {
glCullFace(GL_BACK) // cull back faces glCullFace(GL_BACK) // cull back faces
glFrontFace(GL_CCW) // counter-clockwise triangles are front-facing glFrontFace(GL_CCW) // counter-clockwise triangles are front-facing
glDepthFunc(GL_LESS) glDepthFunc(GL_LESS)
glEnable(GL_BLEND)
glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
textureId = loadTexture("/assets/Blocktopia/textures/block/diamond_ore.png") block_atlas.buildAtlas()
// Simple shaders // Simple shaders
val vertexShaderSrc = loadResource("/assets/Blocktopia/shaders/core/terrain.vsh") val vertexShaderSrc = loadResource("/assets/Blocktopia/shaders/core/terrain.vsh")
@@ -123,7 +98,7 @@ object Game {
modelLoc = glGetUniformLocation(shaderProgram, "model") modelLoc = glGetUniformLocation(shaderProgram, "model")
viewLoc = glGetUniformLocation(shaderProgram, "view") viewLoc = glGetUniformLocation(shaderProgram, "view")
projLoc = glGetUniformLocation(shaderProgram, "projection") projLoc = glGetUniformLocation(shaderProgram, "projection")
session.World = World(Player(Vector3d(0.0, 400.0, 0.0), 0f, 0f), session) session.World = World(Player(Vector3d(0.0, 500.0, 0.0), 0f, 0f), session)
session.World!!.player.world = session.World session.World!!.player.world = session.World
session.World!!.init() session.World!!.init()
@@ -0,0 +1,4 @@
package dev.wbell.buildtopia.app.game.session.Server
class InternalServer {
}
@@ -0,0 +1,4 @@
package dev.wbell.buildtopia.app.game.session.Server
interface Server {
}
+68 -43
View File
@@ -4,7 +4,7 @@ import OpenSimplex2S.OpenSimplex2S
import dev.wbell.buildtopia.app.game.Game import dev.wbell.buildtopia.app.game.Game
import dev.wbell.buildtopia.app.game.camera.Camera import dev.wbell.buildtopia.app.game.camera.Camera
import dev.wbell.buildtopia.app.game.session.Session 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.Block.BlockRegistry
import dev.wbell.buildtopia.app.game.session.world.chunk.BlockAccessor 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.Chunk
import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection
@@ -16,14 +16,36 @@ import kotlinx.coroutines.*
import org.joml.Matrix4f import org.joml.Matrix4f
import org.joml.Vector2i import org.joml.Vector2i
import org.joml.Vector3d import org.joml.Vector3d
import org.lwjgl.glfw.GLFW.* import org.lwjgl.glfw.GLFW.glfwGetTime
import org.lwjgl.opengl.GL20.* import org.lwjgl.opengl.GL20.*
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.PriorityBlockingQueue
import kotlin.math.PI import kotlin.math.PI
//fun main() {
// val worldSeed: Long = 1L
// val version = MCVersion.v1_16_5
// val dimension = Dimension.OVERWORLD
//
// val biomeSource = BiomeSource.of(dimension, version, worldSeed)
// val terrainGenerator = TerrainGenerator.of(biomeSource)
// ?: throw IllegalStateException("Failed to create TerrainGenerator")
//
// val block: Optional<McBlock?>? = terrainGenerator.getBlockAt(0, 0, 0)
// val column = terrainGenerator.getColumnAt(0, 0)
//
// val surfaceGenHeight: Int = terrainGenerator.getFirstHeightInColumn(0, 0, TerrainGenerator.WORLD_SURFACE_WG)
// val oceanGenHeight: Int = terrainGenerator.getFirstHeightInColumn(0, 0, TerrainGenerator.OCEAN_FLOOR_WG)
// val surfaceBlockIn: Int = terrainGenerator.getHeightInGround(0, 0)
// val surfaceBlockAbove: Int = terrainGenerator.getHeightOnGround(0, 0)
//
// println("Block at (0,0,0): $block")
// println("Surface height: $surfaceGenHeight")
// println("Ocean floor height: $oceanGenHeight")
//}
fun floorDiv(a: Int, b: Int): Int { fun floorDiv(a: Int, b: Int): Int {
// rounds down (towards -∞), unlike / which rounds toward 0 // rounds down (towards -∞), unlike / which rounds toward 0
var div = a / b var div = a / b
@@ -50,11 +72,13 @@ class World(val player: Player, val session: Session) {
private var isActive = true private var isActive = true
var lastTick = glfwGetTime() var lastTick = glfwGetTime()
var dayNightLoc = glGetUniformLocation(Game.shaderProgram, "dayNight") var dayNightLoc = glGetUniformLocation(Game.shaderProgram, "dayNight")
var dayNightTick = 0 var dayNightTick = 12000
val seed: Long = 1203223
var seed: Long = 12345
fun init() { fun init() {
startPhysics() startPhysics()
startChunkRenderer() startChunkRenderer()
startChunkUpdateRenderer() startChunkUpdateRenderer()
@@ -94,55 +118,56 @@ class World(val player: Player, val session: Session) {
chunksToRender.sortBy { (_, coords) -> (coords.x - centerChunk.x) * (coords.x - centerChunk.x) + (coords.y - centerChunk.y) * (coords.y - centerChunk.y) } chunksToRender.sortBy { (_, coords) -> (coords.x - centerChunk.x) * (coords.x - centerChunk.x) + (coords.y - centerChunk.y) * (coords.y - centerChunk.y) }
var i = 0 var i = 0
for ((key, coords) in chunksToRender) { for ((key, coords) in chunksToRender) {
var peak = -4* ChunkSection.SIZE var peak = -4 * ChunkSection.SIZE
val blockData = Array(ChunkSection.LENGTH * ChunkSection.LENGTH * ChunkSection.LENGTH * 24) { i -> val blockData = Array(ChunkSection.LENGTH * ChunkSection.LENGTH * ChunkSection.LENGTH * 24) { i ->
val x = i % ChunkSection.LENGTH val x = i % ChunkSection.LENGTH
val z = (i / ChunkSection.LENGTH) % ChunkSection.LENGTH val z = (i / ChunkSection.LENGTH) % ChunkSection.LENGTH
val y = (i / (ChunkSection.LENGTH * ChunkSection.LENGTH)) - 4 * ChunkSection.LENGTH val y = (i / (ChunkSection.LENGTH * ChunkSection.LENGTH)) - 0 * ChunkSection.LENGTH
// world-space coordinates
// World coordinates
val worldX = coords.x * ChunkSection.LENGTH + x val worldX = coords.x * ChunkSection.LENGTH + x
val worldZ = coords.y * ChunkSection.LENGTH + z val worldZ = coords.y * ChunkSection.LENGTH + z
val worldY = y val worldY = y
// --- Biome / height noise --- // ----- TERRAIN HEIGHT -----
val biomeFactor = OpenSimplex2S.noise2(seed + 1000, worldX * 0.01, worldZ * 0.01) val cont = OpenSimplex2S.noise2(seed + 1000, worldX * 0.0008, worldZ * 0.0008) // continents/ocean
val baseHeight = 64 + (biomeFactor * 8).toInt() // plains ~56-72 val ridge = OpenSimplex2S.noise2(seed + 2000, worldX * 0.004, worldZ * 0.004) // mountains
val detail = OpenSimplex2S.noise2(seed + 3000, worldX * 0.02, worldZ * 0.02) // small variation
// Hills / mountains // Blend noises together
val hillNoise = OpenSimplex2S.noise2(seed + 2000, worldX * 0.02, worldZ * 0.02) val baseHeight = 64.0 + cont * 30.0
val hillHeight = (hillNoise * 16).toInt() val ridgeHeight = ridge * ridge * 50.0 // squared for more contrast
val terrainHeight = baseHeight + hillHeight val terrainHeight = (baseHeight + ridgeHeight + detail * 3.0).toInt()
// --- Multi-octave 3D caves --- // ----- CAVES -----
fun caveDensity(x: Double, y: Double, z: Double): Double { fun caveDensity(x: Double, y: Double, z: Double): Double {
var density = 0.0 var total = 0.0
var frequency = 0.02 var freq = 0.01
var amplitude = 1.0 var amp = 1.0
for (octave in 0 until 3) { // 3 octaves, can increase for more detail for (octave in 0 until 4) {
density += OpenSimplex2S.noise3_ImproveXY(seed + 3000 + octave * 1000, x * frequency, y * frequency, z * frequency) * amplitude total += OpenSimplex2S.noise3_ImproveXY(seed + 4000 + octave * 500, x * freq, y * freq, z * freq) * amp
frequency *= 2.0 freq *= 2.0
amplitude /= 2.0 amp *= 0.5
} }
return density return total
} }
val density = caveDensity(worldX.toDouble(), worldY.toDouble(), worldZ.toDouble()) val density = caveDensity(worldX.toDouble(), worldY.toDouble(), worldZ.toDouble())
// Optional: reduce cave openings near surface // Caves taper out near the surface
val heightFactor = ((terrainHeight - worldY).toDouble() / 30.0).coerceIn(0.0, 1.0) val heightFactor = ((terrainHeight - worldY).toDouble() / 40.0).coerceIn(0.0, 1.0)
val isCave = density > 0.5 * heightFactor // adjust threshold for more/less caves val isCave = density > 0.6 * heightFactor
// --- Set block --- // ----- BLOCK PLACEMENT -----
val block: Block? = if (worldY <= terrainHeight && !isCave) { val block: Int = if (worldY <= terrainHeight && !isCave) {
when { when {
worldY == terrainHeight -> Block() worldY == terrainHeight -> BlockRegistry.getIndex("minecraft:grass_block")
worldY >= terrainHeight - 3 -> Block() worldY >= terrainHeight - 4 -> BlockRegistry.getIndex("minecraft:dirt")
else -> Block() else -> BlockRegistry.getIndex("minecraft:stone")
} }
} else null } else BlockRegistry.getIndex("minecraft:air")
if (block != 0 && y > peak) peak = y
if (terrainHeight > peak) peak = terrainHeight
block block
} }
chunks[key] = Chunk( chunks[key] = Chunk(
@@ -168,7 +193,7 @@ class World(val player: Player, val session: Session) {
for (chunk in chunks.values) { for (chunk in chunks.values) {
if (chunk.toRenderMesh()) markChunkDirty(chunk) if (chunk.toRenderMesh()) markChunkDirty(chunk)
} }
repeat(4) { repeat(10) {
val next = chunkRenderQueue.poll() ?: return@repeat val next = chunkRenderQueue.poll() ?: return@repeat
val (chunk, _) = next val (chunk, _) = next
chunk.isQueuedForRender = false chunk.isQueuedForRender = false
@@ -190,7 +215,7 @@ class World(val player: Player, val session: Session) {
for (chunk in chunks.values) { for (chunk in chunks.values) {
if (chunk.toUpdateMesh()) markChunkDirtyUpdate(chunk) if (chunk.toUpdateMesh()) markChunkDirtyUpdate(chunk)
} }
repeat(4) { repeat(10) {
val next = chunkUpdateRenderQueue.poll() ?: return@repeat val next = chunkUpdateRenderQueue.poll() ?: return@repeat
val (chunk, _) = next val (chunk, _) = next
chunk.isQueuedForRender = false chunk.isQueuedForRender = false
@@ -233,14 +258,14 @@ class World(val player: Player, val session: Session) {
val blocks = object : BlockAccessor { val blocks = object : BlockAccessor {
override operator fun get(x: Int, y: Int, z: Int): Block? { override operator fun get(x: Int, y: Int, z: Int): Int {
return getChunk(floorDiv(x, ChunkSection.LENGTH), floorDiv(z, ChunkSection.LENGTH))?.blocks[floorMod( return getChunk(floorDiv(x, ChunkSection.LENGTH), floorDiv(z, ChunkSection.LENGTH))?.blocks[floorMod(
x, x,
ChunkSection.LENGTH ChunkSection.LENGTH
), y, floorMod(z, ChunkSection.LENGTH)] ), y, floorMod(z, ChunkSection.LENGTH)]?:0
} }
override operator fun set(x: Int, y: Int, z: Int, value: Block?) { override operator fun set(x: Int, y: Int, z: Int, value: Int) {
getChunk(floorDiv(x, ChunkSection.LENGTH), floorDiv(z, ChunkSection.LENGTH))?.blocks[floorMod( getChunk(floorDiv(x, ChunkSection.LENGTH), floorDiv(z, ChunkSection.LENGTH))?.blocks[floorMod(
x, x,
ChunkSection.LENGTH ChunkSection.LENGTH
@@ -314,7 +339,7 @@ class World(val player: Player, val session: Session) {
(player.position.x / ChunkSection.LENGTH).toInt(), (player.position.x / ChunkSection.LENGTH).toInt(),
(player.position.z / ChunkSection.LENGTH).toInt() (player.position.z / ChunkSection.LENGTH).toInt()
) )
val renderDistance = Settings.get(SettingKey.RENDERDISTANCE) ?: 32 val renderDistance = Settings[SettingKey.RENDERDISTANCE] ?: 32
// --- Unload distant chunks --- // --- Unload distant chunks ---
val chunksToUnload = chunks.values.filter { chunk -> val chunksToUnload = chunks.values.filter { chunk ->
@@ -333,7 +358,7 @@ class World(val player: Player, val session: Session) {
val chunkCoords = mutableListOf<Pair<Long, Vector2i>>() val chunkCoords = mutableListOf<Pair<Long, Vector2i>>()
for (dx in -renderDistance..renderDistance) { for (dx in -renderDistance..renderDistance) {
for (dz in -renderDistance..renderDistance) { for (dz in -renderDistance..renderDistance) {
if (dx*dx + dz*dz > renderDistance*renderDistance) continue // circular radius if (dx * dx + dz * dz > renderDistance * renderDistance) continue // circular radius
val chunkX = centerChunk.x + dx val chunkX = centerChunk.x + dx
val chunkZ = centerChunk.y + dz val chunkZ = centerChunk.y + dz
val key = packChunkKey(chunkX, chunkZ) val key = packChunkKey(chunkX, chunkZ)
@@ -345,7 +370,7 @@ class World(val player: Player, val session: Session) {
chunkCoords.sortBy { (_, coords) -> chunkCoords.sortBy { (_, coords) ->
val dx = coords.x - centerChunk.x val dx = coords.x - centerChunk.x
val dz = coords.y - centerChunk.y val dz = coords.y - centerChunk.y
dx*dx + dz*dz dx * dx + dz * dz
} }
// --- Load/render chunks in order --- // --- Load/render chunks in order ---
@@ -1,9 +1,33 @@
package dev.wbell.buildtopia.app.game.session.world.chunk.Block package dev.wbell.buildtopia.app.game.session.world.chunk.Block
import dev.wbell.buildtopia.app.Resource_Pack_Loader.TextureAtlas.TextureAtlas
import dev.wbell.buildtopia.app.game.Game import dev.wbell.buildtopia.app.game.Game
import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection
import org.joml.Vector2i import org.joml.Vector2i
data class BlockType(
val id: String,
val name: String,
val texture: String,
val transparent: Boolean = false,
val solid: Boolean = true,
)
object BlockRegistry {
private val blocksById = mutableMapOf<String, BlockType>()
private val idByIndex = mutableListOf<String>() // optional compact storage
fun register( block: BlockType) {
if (block.id in blocksById) error("Block ID '${block.id}' already registered!")
blocksById[block.id] = block
idByIndex.add(block.id)
}
fun get(id: String): BlockType? = blocksById[id]
fun getByIndex(index: Int): BlockType? = blocksById[idByIndex.getOrNull(index)]
fun getIndex(id: String): Int = idByIndex.indexOf(id)
}
fun getVertexLight(x: Int, y: Int, z: Int, chunk: Vector2i, offsets: List<Triple<Int, Int, Int>>): Pair<Float, Float> { fun getVertexLight(x: Int, y: Int, z: Int, chunk: Vector2i, offsets: List<Triple<Int, Int, Int>>): Pair<Float, Float> {
var sunTotal = 0 var sunTotal = 0
var blockTotal = 0 var blockTotal = 0
@@ -25,7 +49,7 @@ fun getVertexLight(x: Int, y: Int, z: Int, chunk: Vector2i, offsets: List<Triple
} }
// @formatter:off // @formatter:off
fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2i, cubeScale: Float, face: String) { fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, uv: TextureAtlas.UV, chunk: Vector2i, cubeScale: Float, face: String) {
val half = cubeScale / 2f val half = cubeScale / 2f
val lightR = 0f val lightR = 0f
@@ -51,13 +75,13 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2
val (trR, trG) = getVertexLight(x, y, z, chunk, frontTopRight) val (trR, trG) = getVertexLight(x, y, z, chunk, frontTopRight)
val (tlR, tlG) = getVertexLight(x, y, z, chunk, frontTopLeft) val (tlR, tlG) = getVertexLight(x, y, z, chunk, frontTopLeft)
vertices.addAll(listOf( vertices.addAll(listOf(
x - half, y - half, z + half, 0f, 1f, blR, blG, // bottom-left x - half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, blR, blG, // bottom-left
x + half, y - half, z + half, 1f, 1f, brR, brG, // bottom-right x + half, y - half, z + half, uv.bottomRight.x, uv.bottomRight.y, brR, brG, // bottom-right
x + half, y + half, z + half, 1f, 0f, trR, trG, // top-right x + half, y + half, z + half, uv.bottomRight.x, uv.topLeft.y, trR, trG, // top-right
x + half, y + half, z + half, 1f, 0f, trR, trG, x + half, y + half, z + half, uv.bottomRight.x, uv.topLeft.y, trR, trG,
x - half, y + half, z + half, 0f, 0f, tlR, tlG, // top-left x - half, y + half, z + half, uv.topLeft.x, uv.topLeft.y, tlR, tlG, // top-left
x - half, y - half, z + half, 0f, 1f, blR, blG, x - half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, blR, blG,
))} ))}
// === BACK FACE === // === BACK FACE ===
"back" -> { "back" -> {
@@ -80,13 +104,13 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2
val (brR, brG) = getVertexLight(x, y, z, chunk, backBottomRight) val (brR, brG) = getVertexLight(x, y, z, chunk, backBottomRight)
vertices.addAll(listOf( vertices.addAll(listOf(
x - half, y - half, z - half, 1f, 1f, blR, blG, x - half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, blR, blG,
x - half, y + half, z - half, 1f, 0f, tlR, tlG, x - half, y + half, z - half, uv.bottomRight.x, uv.topLeft.y, tlR, tlG,
x + half, y + half, z - half, 0f, 0f, trR, trG, x + half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, trR, trG,
x + half, y + half, z - half, 0f, 0f, trR, trG, x + half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, trR, trG,
x + half, y - half, z - half, 0f, 1f, brR, brG, x + half, y - half, z - half, uv.topLeft.x, uv.bottomRight.y, brR, brG,
x - half, y - half, z - half, 1f, 1f, blR, blG, x - half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, blR, blG,
)) ))
} }
@@ -111,13 +135,13 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2
val (tbR, tbG) = getVertexLight(x, y, z, chunk, leftTopBack) val (tbR, tbG) = getVertexLight(x, y, z, chunk, leftTopBack)
vertices.addAll(listOf( vertices.addAll(listOf(
x - half, y - half, z - half, 0f, 1f, bbR, bbG, x - half, y - half, z - half, uv.topLeft.x, uv.bottomRight.y, bbR, bbG,
x - half, y - half, z + half, 1f, 1f, bfR, bfG, x - half, y - half, z + half, uv.bottomRight.x, uv.bottomRight.y, bfR, bfG,
x - half, y + half, z + half, 1f, 0f, tfR, tfG, x - half, y + half, z + half, uv.bottomRight.x, uv.topLeft.y, tfR, tfG,
x - half, y + half, z + half, 1f, 0f, tfR, tfG, x - half, y + half, z + half, uv.bottomRight.x, uv.topLeft.y, tfR, tfG,
x - half, y + half, z - half, 0f, 0f, tbR, tbG, x - half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, tbR, tbG,
x - half, y - half, z - half, 0f, 1f, bbR, bbG, x - half, y - half, z - half, uv.topLeft.x, uv.bottomRight.y, bbR, bbG,
)) ))
} }
@@ -142,13 +166,13 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2
val (tbR, tbG) = getVertexLight(x, y, z, chunk, rightTopBack) val (tbR, tbG) = getVertexLight(x, y, z, chunk, rightTopBack)
vertices.addAll(listOf( vertices.addAll(listOf(
x + half, y - half, z - half, 1f, 1f, bbR, bbG, x + half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, bbR, bbG,
x + half, y + half, z + half, 0f, 0f, tfR, tfG, x + half, y + half, z + half, uv.topLeft.x, uv.topLeft.y, tfR, tfG,
x + half, y - half, z + half, 0f, 1f, bfR, bfG, x + half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, bfR, bfG,
x + half, y + half, z + half, 0f, 0f, tfR, tfG, x + half, y + half, z + half, uv.topLeft.x, uv.topLeft.y, tfR, tfG,
x + half, y - half, z - half, 1f, 1f, bbR, bbG, x + half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, bbR, bbG,
x + half, y + half, z - half, 1f, 0f, tbR, tbG, x + half, y + half, z - half, uv.bottomRight.x, uv.topLeft.y, tbR, tbG,
)) ))
} }
@@ -173,13 +197,13 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2
val (brR, brG) = getVertexLight(x, y, z, chunk, topBackRight) val (brR, brG) = getVertexLight(x, y, z, chunk, topBackRight)
vertices.addAll(listOf( vertices.addAll(listOf(
x - half, y + half, z - half, 0f, 0f, blR, blG, x - half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, blR, blG,
x - half, y + half, z + half, 0f, 1f, flR, flG, x - half, y + half, z + half, uv.topLeft.x, uv.bottomRight.y, flR, flG,
x + half, y + half, z + half, 1f, 1f, frR, frG, x + half, y + half, z + half, uv.bottomRight.x, uv.bottomRight.y, frR, frG,
x + half, y + half, z + half, 1f, 1f, frR, frG, x + half, y + half, z + half, uv.bottomRight.x, uv.bottomRight.y, frR, frG,
x + half, y + half, z - half, 1f, 0f, brR, brG, x + half, y + half, z - half, uv.bottomRight.x, uv.topLeft.y, brR, brG,
x - half, y + half, z - half, 0f, 0f, blR, blG, x - half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, blR, blG,
)) ))
} }
@@ -204,19 +228,15 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, chunk: Vector2
val (brR, brG) = getVertexLight(x, y, z, chunk, bottomBackRight) val (brR, brG) = getVertexLight(x, y, z, chunk, bottomBackRight)
vertices.addAll(listOf( vertices.addAll(listOf(
x - half, y - half, z - half, 1f, 0f, blR, blG, x - half, y - half, z - half, uv.bottomRight.x, uv.topLeft.y, blR, blG,
x + half, y - half, z + half, 0f, 1f, frR, frG, x + half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, frR, frG,
x - half, y - half, z + half, 1f, 1f, flR, flG, x - half, y - half, z + half, uv.bottomRight.x, uv.bottomRight.y, flR, flG,
x - half, y - half, z - half, 1f, 0f, blR, blG, x - half, y - half, z - half, uv.bottomRight.x, uv.topLeft.y, blR, blG,
x + half, y - half, z - half, 0f, 0f, brR, brG, x + half, y - half, z - half, uv.topLeft.x, uv.topLeft.y, brR, brG,
x + half, y - half, z + half, 0f, 1f, frR, frG, x + half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, frR, frG,
)) ))
} }
} }
} }
// @formatter:on // @formatter:on
class Block {
}
@@ -2,7 +2,6 @@ package dev.wbell.buildtopia.app.game.session.world.chunk
import dev.wbell.buildtopia.app.game.Game import dev.wbell.buildtopia.app.game.Game
import dev.wbell.buildtopia.app.game.session.world.World 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.chunk.Block.addFace
import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection.Companion.LENGTH import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection.Companion.LENGTH
import dev.wbell.buildtopia.app.game.session.world.floorDiv import dev.wbell.buildtopia.app.game.session.world.floorDiv
@@ -24,8 +23,8 @@ import java.util.Collections
interface BlockAccessor { interface BlockAccessor {
operator fun get(x: Int, y: Int, z: Int): Block? operator fun get(x: Int, y: Int, z: Int): Int
operator fun set(x: Int, y: Int, z: Int, value: Block?) operator fun set(x: Int, y: Int, z: Int, value: Int)
} }
interface LightAccessor { interface LightAccessor {
@@ -33,7 +32,7 @@ interface LightAccessor {
operator fun set(x: Int, y: Int, z: Int, value: Int) operator fun set(x: Int, y: Int, z: Int, value: Int)
} }
class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: Int) { class ChunkSection(val blockData: Array<Int>, val chunk: Chunk, val index: Int) {
companion object { companion object {
const val LENGTH = 16 const val LENGTH = 16
const val SIZE = LENGTH * LENGTH * LENGTH const val SIZE = LENGTH * LENGTH * LENGTH
@@ -58,7 +57,7 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
) )
// Seed the queue with all blocks that have sunlight > 1 // Seed the queue with all blocks that have sunlight > 1
var blocked = Array(LENGTH*LENGTH){1} val blocked = Array(LENGTH*LENGTH){1}
var toBreak = false var toBreak = false
for (y in LENGTH - 1 downTo 0) { for (y in LENGTH - 1 downTo 0) {
val worldY = y + index * LENGTH - chunk.ySectionsOffset * LENGTH val worldY = y + index * LENGTH - chunk.ySectionsOffset * LENGTH
@@ -69,7 +68,7 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
val worldX = x + chunk.coords.x * LENGTH val worldX = x + chunk.coords.x * LENGTH
val worldZ = z + chunk.coords.y * LENGTH val worldZ = z + chunk.coords.y * LENGTH
val light = sunLights[x, y, z] val light = sunLights[x, y, z]
if (blocks[x,y,z] != null) { if (blocks[x,y,z] != 0) {
blocked[posInBlocked] = 0 blocked[posInBlocked] = 0
if (blocked.sum()==0) { if (blocked.sum()==0) {
toBreak = true toBreak = true
@@ -102,7 +101,7 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
if (newLightLevel <= 0) continue if (newLightLevel <= 0) continue
// skip if block is solid or light is already >= newLightLevel // skip if block is solid or light is already >= newLightLevel
if (chunk.world.blocks[nx, ny, nz] != null) continue if (chunk.world.blocks[nx, ny, nz] != 0) continue
if (chunk.world.sunLights[nx, ny, nz] >= newLightLevel) continue if (chunk.world.sunLights[nx, ny, nz] >= newLightLevel) continue
// update light immediately and enqueue // update light immediately and enqueue
@@ -339,13 +338,13 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
} }
val blocks = object : BlockAccessor { val blocks = object : BlockAccessor {
override operator fun get(x: Int, y: Int, z: Int): Block? { override operator fun get(x: Int, y: Int, z: Int): Int {
val index = x + z * LENGTH + y * LENGTH * LENGTH val index = x + z * LENGTH + y * LENGTH * LENGTH
if ((x !in 0..<LENGTH) || (z !in 0..<LENGTH) || (y !in 0..<LENGTH)) return null if ((x !in 0..<LENGTH) || (z !in 0..<LENGTH) || (y !in 0..<LENGTH)) return 0
return blockData[index] return blockData[index]
} }
override operator fun set(x: Int, y: Int, z: Int, value: Block?) { override operator fun set(x: Int, y: Int, z: Int, value: Int) {
val index = x + z * LENGTH + y * LENGTH * LENGTH val index = x + z * LENGTH + y * LENGTH * LENGTH
if (index < 0 || index >= blockData.size) return if (index < 0 || index >= blockData.size) return
blockData[index] = value blockData[index] = value
@@ -455,7 +454,7 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
val vertexCount = vaoSize / 7 // <-- changed val vertexCount = vaoSize / 7 // <-- changed
// Bind texture // Bind texture
glBindTexture(GL_TEXTURE_2D, Game.textureId) glBindTexture(GL_TEXTURE_2D, Game.block_atlas.textureId)
glUniform1i(glGetUniformLocation(Game.shaderProgram, "texture1"), 0) glUniform1i(glGetUniformLocation(Game.shaderProgram, "texture1"), 0)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
@@ -485,59 +484,65 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
for (sectionY in 0..<LENGTH) { for (sectionY in 0..<LENGTH) {
val y = sectionY + LENGTH * index - chunk.ySectionsOffset * LENGTH val y = sectionY + LENGTH * index - chunk.ySectionsOffset * LENGTH
for (z in 0..<LENGTH) { for (z in 0..<LENGTH) {
if (blocks[x, sectionY, z] == null) continue; if (blocks[x, sectionY, z] == 0) continue;
val worldX = x + chunk.coords.x * LENGTH val worldX = x + chunk.coords.x * LENGTH
val worldZ = z + chunk.coords.y * LENGTH val worldZ = z + chunk.coords.y * LENGTH
if (chunk.world.blocks[worldX, y + 1, worldZ] == null) addFace( if (chunk.world.blocks[worldX, y + 1, worldZ] == 0) addFace(
vertices, vertices,
x, x,
y, y,
z, z,
Game.block_atlas.getUV("crafting_table_top.png"),
chunk.coords, chunk.coords,
1f, 1f,
"top" "top"
) )
if (chunk.world.blocks[worldX, y - 1, worldZ] == null) addFace( if (chunk.world.blocks[worldX, y - 1, worldZ] == 0) addFace(
vertices, vertices,
x, x,
y, y,
z, z,
Game.block_atlas.getUV("oak_planks.png"),
chunk.coords, chunk.coords,
1f, 1f,
"bottom" "bottom"
) )
if (chunk.world.blocks[worldX + 1, y, worldZ] == null) addFace( if (chunk.world.blocks[worldX + 1, y, worldZ] == 0) addFace(
vertices, vertices,
x, x,
y, y,
z, z,
Game.block_atlas.getUV("crafting_table_side.png"),
chunk.coords, chunk.coords,
1f, 1f,
"right" "right"
) )
if (chunk.world.blocks[worldX - 1, y, worldZ] == null) addFace( if (chunk.world.blocks[worldX - 1, y, worldZ] == 0) addFace(
vertices, vertices,
x, x,
y, y,
z, z,
Game.block_atlas.getUV("crafting_table_side.png"),
chunk.coords, chunk.coords,
1f, 1f,
"left" "left"
) )
if (chunk.world.blocks[worldX, y, worldZ + 1] == null) addFace( if (chunk.world.blocks[worldX, y, worldZ + 1] == 0) addFace(
vertices, vertices,
x, x,
y, y,
z, z,
Game.block_atlas.getUV("crafting_table_front.png"),
chunk.coords, chunk.coords,
1f, 1f,
"front" "front"
) )
if (chunk.world.blocks[worldX, y, worldZ - 1] == null) addFace( if (chunk.world.blocks[worldX, y, worldZ - 1] == 0) addFace(
vertices, vertices,
x, x,
y, y,
z, z,
Game.block_atlas.getUV("crafting_table_front.png"),
chunk.coords, chunk.coords,
1f, 1f,
"back" "back"
@@ -562,7 +567,7 @@ class Chunk(
val coords: Vector2i, val coords: Vector2i,
val numberOfSections: Int, val numberOfSections: Int,
val ySectionsOffset: Int, val ySectionsOffset: Int,
var blockData: Array<Block?>?, var blockData: Array<Int>?,
val peak: Int val peak: Int
) { ) {
var sections: Array<ChunkSection> = Array(numberOfSections) { sectionIndex -> var sections: Array<ChunkSection> = Array(numberOfSections) { sectionIndex ->
@@ -628,12 +633,12 @@ class Chunk(
} }
val blocks = object : BlockAccessor { val blocks = object : BlockAccessor {
override operator fun get(x: Int, y: Int, z: Int): Block? { override operator fun get(x: Int, y: Int, z: Int): Int {
if (!loaded) return null if (!loaded) return 0
return getChunkSection(y)?.blocks[x, floorMod(y, LENGTH), z] return getChunkSection(y)?.blocks[x, floorMod(y, LENGTH), z]?:0
} }
override operator fun set(x: Int, y: Int, z: Int, value: Block?) { override operator fun set(x: Int, y: Int, z: Int, value: Int) {
if (!loaded) return if (!loaded) return
getChunkSection(y)?.blocks[x, floorMod(y, LENGTH), z] = value getChunkSection(y)?.blocks[x, floorMod(y, LENGTH), z] = value
} }
@@ -62,7 +62,7 @@ class Player(val position: Vector3d, var pitch: Float, var yaw: Float) {
for (y in minY..maxY) { for (y in minY..maxY) {
for (z in minZ..maxZ) { for (z in minZ..maxZ) {
val block = world!!.blocks[x, y, z] val block = world!!.blocks[x, y, z]
if (block != null) { if (block != 0) {
boxes.add( boxes.add(
AABB( AABB(
x - 0.5, y.toDouble(), z - 0.5, x - 0.5, y.toDouble(), z - 0.5,
@@ -183,7 +183,12 @@ class Player(val position: Vector3d, var pitch: Float, var yaw: Float) {
} }
fun getCamera(alpha: Double): Camera { fun getCamera(alpha: Double): Camera {
val targetFovModifier = if (isSprinting) 1.1 else 1.0 var targetFovModifier = if (isSprinting) 1.1 else 1.0
if (glfwGetKey(Game.window!!, GLFW_KEY_C) == GLFW_PRESS) {
targetFovModifier = 0.2
}
// gradually move current toward target, just like Minecraft // gradually move current toward target, just like Minecraft
fovModifierCurrent += (targetFovModifier - fovModifierCurrent) * 0.1 fovModifierCurrent += (targetFovModifier - fovModifierCurrent) * 0.1
@@ -32,5 +32,6 @@ void main() {
vec3 factor = clamp(0.5 - light, 0.0, 1.0); // only affects darker areas vec3 factor = clamp(0.5 - light, 0.0, 1.0); // only affects darker areas
light = light + shadowLift * factor; light = light + shadowLift * factor;
FragColor = vec4(texColor.rgb * light, 1.0); // Apply lighting to RGB but preserve alpha from texture
FragColor = vec4(texColor.rgb * light, texColor.a);
} }
+2
View File
@@ -22,5 +22,7 @@ plugins {
// Learn more about structuring projects with Gradle - https://docs.gradle.org/8.7/userguide/multi_project_builds.html // Learn more about structuring projects with Gradle - https://docs.gradle.org/8.7/userguide/multi_project_builds.html
include(":app") include(":app")
include(":utils") include(":utils")
include("mc_terrain_java")
project(":mc_terrain_java").projectDir = file("lib/mc_terrain_java")
rootProject.name = "Buildtopia" rootProject.name = "Buildtopia"