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$/app" />
<option value="$PROJECT_DIR$/buildSrc" />
<option value="$PROJECT_DIR$/lib/mc_terrain_java" />
<option value="$PROJECT_DIR$/utils" />
</set>
</option>
+1 -1
View File
@@ -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_17" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
</project>
Generated
+1
View File
@@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/lib/mc_terrain_java" vcs="Git" />
</component>
</project>
+36 -5
View File
@@ -4,14 +4,27 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<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/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/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/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$/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>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -30,8 +43,14 @@
<activation />
</task>
<projects_view>
<option name="groupModules" value="false" />
<tree_state>
<expand />
<expand>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Buildtopia" type="f1a62948:ProjectNode" />
</path>
</expand>
<select />
</tree_state>
</projects_view>
@@ -42,6 +61,8 @@
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
<option value="Kotlin Interface" />
<option value="Kotlin Class" />
</list>
</option>
</component>
@@ -81,7 +102,8 @@
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&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 name="RecentsManager">
@@ -167,7 +189,15 @@
<option name="project" value="LOCAL" />
<updated>1759638464558</updated>
</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 />
</component>
<component name="VcsManagerConfiguration">
@@ -175,6 +205,7 @@
<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 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>
</project>
+12
View File
@@ -9,6 +9,11 @@ plugins {
}
val lwjglVersion = "3.3.6"
repositories {
mavenCentral()
maven(url = "https://maven.seedfinding.com") // ✅ use url= keyword
}
dependencies {
implementation(project(":utils"))
@@ -22,6 +27,13 @@ dependencies {
implementation("org.lwjgl:lwjgl-glfw:$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
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:natives-linux")
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:natives-windows")
+1 -3
View File
@@ -1,9 +1,7 @@
package dev.wbell.buildtopia.app
import dev.wbell.buildtopia.app.game.Game
import org.joml.Matrix4f
import org.lwjgl.glfw.GLFW.*
import org.lwjgl.opengl.GL11.*
import org.lwjgl.opengl.GL20.*
fun loadResource(path: String): String {
@@ -35,7 +33,7 @@ fun main() {
// Camera parameters
var startTime = glfwGetTime()
val startTime = glfwGetTime()
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
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.game.session.Session
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.loadResource
import org.joml.Matrix4f
import org.joml.Vector3d
import org.lwjgl.BufferUtils
import org.lwjgl.glfw.GLFW.*
import org.lwjgl.opengl.GL
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.opengl.GL13.*
import org.lwjgl.opengl.GL20.glUniformMatrix4fv
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
@@ -67,7 +32,7 @@ object Game {
var lastY: Double? = null
}
var textureId = 0
val block_atlas = TextureAtlas(ClasspathResourceProvider("/assets/Blocktopia/textures/block", this))
var shaderProgram = 0
var modelLoc = 0
@@ -91,7 +56,12 @@ object Game {
}
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")
glfwWindowHint(GLFW_SAMPLES, 4)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
@@ -109,8 +79,13 @@ object Game {
glCullFace(GL_BACK) // cull back faces
glFrontFace(GL_CCW) // counter-clockwise triangles are front-facing
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
val vertexShaderSrc = loadResource("/assets/Blocktopia/shaders/core/terrain.vsh")
@@ -123,7 +98,7 @@ object Game {
modelLoc = glGetUniformLocation(shaderProgram, "model")
viewLoc = glGetUniformLocation(shaderProgram, "view")
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!!.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.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.Block.BlockRegistry
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
@@ -16,14 +16,36 @@ import kotlinx.coroutines.*
import org.joml.Matrix4f
import org.joml.Vector2i
import org.joml.Vector3d
import org.lwjgl.glfw.GLFW.*
import org.lwjgl.glfw.GLFW.glfwGetTime
import org.lwjgl.opengl.GL20.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.PriorityBlockingQueue
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 {
// rounds down (towards -∞), unlike / which rounds toward 0
var div = a / b
@@ -50,11 +72,13 @@ class World(val player: Player, val session: Session) {
private var isActive = true
var lastTick = glfwGetTime()
var dayNightLoc = glGetUniformLocation(Game.shaderProgram, "dayNight")
var dayNightTick = 0
var dayNightTick = 12000
val seed: Long = 1203223
var seed: Long = 12345
fun init() {
startPhysics()
startChunkRenderer()
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) }
var i = 0
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 x = i % 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 worldZ = coords.y * ChunkSection.LENGTH + z
val worldY = y
// --- Biome / height noise ---
val biomeFactor = OpenSimplex2S.noise2(seed + 1000, worldX * 0.01, worldZ * 0.01)
val baseHeight = 64 + (biomeFactor * 8).toInt() // plains ~56-72
// ----- TERRAIN HEIGHT -----
val cont = OpenSimplex2S.noise2(seed + 1000, worldX * 0.0008, worldZ * 0.0008) // continents/ocean
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
val hillNoise = OpenSimplex2S.noise2(seed + 2000, worldX * 0.02, worldZ * 0.02)
val hillHeight = (hillNoise * 16).toInt()
val terrainHeight = baseHeight + hillHeight
// Blend noises together
val baseHeight = 64.0 + cont * 30.0
val ridgeHeight = ridge * ridge * 50.0 // squared for more contrast
val terrainHeight = (baseHeight + ridgeHeight + detail * 3.0).toInt()
// --- Multi-octave 3D caves ---
// ----- CAVES -----
fun caveDensity(x: Double, y: Double, z: Double): Double {
var density = 0.0
var frequency = 0.02
var amplitude = 1.0
for (octave in 0 until 3) { // 3 octaves, can increase for more detail
density += OpenSimplex2S.noise3_ImproveXY(seed + 3000 + octave * 1000, x * frequency, y * frequency, z * frequency) * amplitude
frequency *= 2.0
amplitude /= 2.0
var total = 0.0
var freq = 0.01
var amp = 1.0
for (octave in 0 until 4) {
total += OpenSimplex2S.noise3_ImproveXY(seed + 4000 + octave * 500, x * freq, y * freq, z * freq) * amp
freq *= 2.0
amp *= 0.5
}
return density
return total
}
val density = caveDensity(worldX.toDouble(), worldY.toDouble(), worldZ.toDouble())
// Optional: reduce cave openings near surface
val heightFactor = ((terrainHeight - worldY).toDouble() / 30.0).coerceIn(0.0, 1.0)
val isCave = density > 0.5 * heightFactor // adjust threshold for more/less caves
// Caves taper out near the surface
val heightFactor = ((terrainHeight - worldY).toDouble() / 40.0).coerceIn(0.0, 1.0)
val isCave = density > 0.6 * heightFactor
// --- Set block ---
val block: Block? = if (worldY <= terrainHeight && !isCave) {
// ----- BLOCK PLACEMENT -----
val block: Int = if (worldY <= terrainHeight && !isCave) {
when {
worldY == terrainHeight -> Block()
worldY >= terrainHeight - 3 -> Block()
else -> Block()
worldY == terrainHeight -> BlockRegistry.getIndex("minecraft:grass_block")
worldY >= terrainHeight - 4 -> BlockRegistry.getIndex("minecraft:dirt")
else -> BlockRegistry.getIndex("minecraft:stone")
}
} else null
if (terrainHeight > peak) peak = terrainHeight
} else BlockRegistry.getIndex("minecraft:air")
if (block != 0 && y > peak) peak = y
block
}
chunks[key] = Chunk(
@@ -168,7 +193,7 @@ class World(val player: Player, val session: Session) {
for (chunk in chunks.values) {
if (chunk.toRenderMesh()) markChunkDirty(chunk)
}
repeat(4) {
repeat(10) {
val next = chunkRenderQueue.poll() ?: return@repeat
val (chunk, _) = next
chunk.isQueuedForRender = false
@@ -190,7 +215,7 @@ class World(val player: Player, val session: Session) {
for (chunk in chunks.values) {
if (chunk.toUpdateMesh()) markChunkDirtyUpdate(chunk)
}
repeat(4) {
repeat(10) {
val next = chunkUpdateRenderQueue.poll() ?: return@repeat
val (chunk, _) = next
chunk.isQueuedForRender = false
@@ -233,14 +258,14 @@ class World(val player: Player, val session: Session) {
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(
x,
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(
x,
ChunkSection.LENGTH
@@ -314,7 +339,7 @@ class World(val player: Player, val session: Session) {
(player.position.x / 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 ---
val chunksToUnload = chunks.values.filter { chunk ->
@@ -333,7 +358,7 @@ class World(val player: Player, val session: Session) {
val chunkCoords = mutableListOf<Pair<Long, Vector2i>>()
for (dx 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 chunkZ = centerChunk.y + dz
val key = packChunkKey(chunkX, chunkZ)
@@ -345,7 +370,7 @@ class World(val player: Player, val session: Session) {
chunkCoords.sortBy { (_, coords) ->
val dx = coords.x - centerChunk.x
val dz = coords.y - centerChunk.y
dx*dx + dz*dz
dx * dx + dz * dz
}
// --- Load/render chunks in order ---
@@ -1,9 +1,33 @@
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.session.world.chunk.ChunkSection
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> {
var sunTotal = 0
var blockTotal = 0
@@ -25,7 +49,7 @@ fun getVertexLight(x: Int, y: Int, z: Int, chunk: Vector2i, offsets: List<Triple
}
// @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 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 (tlR, tlG) = getVertexLight(x, y, z, chunk, frontTopLeft)
vertices.addAll(listOf(
x - half, y - half, z + half, 0f, 1f, blR, blG, // bottom-left
x + half, y - half, z + half, 1f, 1f, brR, brG, // bottom-right
x + half, y + half, z + half, 1f, 0f, trR, trG, // top-right
x - half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, blR, blG, // bottom-left
x + half, y - half, z + half, uv.bottomRight.x, uv.bottomRight.y, brR, brG, // bottom-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, 0f, 0f, tlR, tlG, // top-left
x - half, y - half, z + half, 0f, 1f, blR, blG,
x + half, y + half, z + half, uv.bottomRight.x, uv.topLeft.y, trR, trG,
x - half, y + half, z + half, uv.topLeft.x, uv.topLeft.y, tlR, tlG, // top-left
x - half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, blR, blG,
))}
// === BACK FACE ===
"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)
vertices.addAll(listOf(
x - half, y - half, z - half, 1f, 1f, blR, blG,
x - half, y + half, z - half, 1f, 0f, tlR, tlG,
x + half, y + half, z - half, 0f, 0f, trR, trG,
x - half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, blR, blG,
x - half, y + half, z - half, uv.bottomRight.x, uv.topLeft.y, tlR, tlG,
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, 0f, 1f, brR, brG,
x - half, y - half, z - half, 1f, 1f, blR, blG,
x + half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, trR, trG,
x + half, y - half, z - half, uv.topLeft.x, uv.bottomRight.y, brR, brG,
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)
vertices.addAll(listOf(
x - half, y - half, z - half, 0f, 1f, bbR, bbG,
x - half, y - half, z + half, 1f, 1f, bfR, bfG,
x - half, y + half, z + half, 1f, 0f, tfR, tfG,
x - half, y - half, z - half, uv.topLeft.x, uv.bottomRight.y, bbR, bbG,
x - half, y - half, z + half, uv.bottomRight.x, uv.bottomRight.y, bfR, bfG,
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, 0f, 0f, tbR, tbG,
x - half, y - half, z - half, 0f, 1f, bbR, bbG,
x - half, y + half, z + half, uv.bottomRight.x, uv.topLeft.y, tfR, tfG,
x - half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, tbR, tbG,
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)
vertices.addAll(listOf(
x + half, y - half, z - half, 1f, 1f, bbR, bbG,
x + half, y + half, z + half, 0f, 0f, tfR, tfG,
x + half, y - half, z + half, 0f, 1f, bfR, bfG,
x + half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, bbR, bbG,
x + half, y + half, z + half, uv.topLeft.x, uv.topLeft.y, tfR, tfG,
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, 1f, 1f, bbR, bbG,
x + half, y + half, z - half, 1f, 0f, tbR, tbG,
x + half, y + half, z + half, uv.topLeft.x, uv.topLeft.y, tfR, tfG,
x + half, y - half, z - half, uv.bottomRight.x, uv.bottomRight.y, bbR, bbG,
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)
vertices.addAll(listOf(
x - half, y + half, z - half, 0f, 0f, blR, blG,
x - half, y + half, z + half, 0f, 1f, flR, flG,
x + half, y + half, z + half, 1f, 1f, frR, frG,
x - half, y + half, z - half, uv.topLeft.x, uv.topLeft.y, blR, blG,
x - half, y + half, z + half, uv.topLeft.x, uv.bottomRight.y, flR, flG,
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, 1f, 0f, brR, brG,
x - half, y + half, z - half, 0f, 0f, blR, blG,
x + half, y + half, z + half, uv.bottomRight.x, uv.bottomRight.y, frR, frG,
x + half, y + half, z - half, uv.bottomRight.x, uv.topLeft.y, brR, brG,
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)
vertices.addAll(listOf(
x - half, y - half, z - half, 1f, 0f, blR, blG,
x + half, y - half, z + half, 0f, 1f, frR, frG,
x - half, y - half, z + half, 1f, 1f, flR, flG,
x - half, y - half, z - half, uv.bottomRight.x, uv.topLeft.y, blR, blG,
x + half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, frR, frG,
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, 0f, 0f, brR, brG,
x + half, y - half, z + half, 0f, 1f, frR, frG,
x - half, y - half, z - half, uv.bottomRight.x, uv.topLeft.y, blR, blG,
x + half, y - half, z - half, uv.topLeft.x, uv.topLeft.y, brR, brG,
x + half, y - half, z + half, uv.topLeft.x, uv.bottomRight.y, frR, frG,
))
}
}
}
// @formatter:on
class Block {
}
// @formatter:on
@@ -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.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.ChunkSection.Companion.LENGTH
import dev.wbell.buildtopia.app.game.session.world.floorDiv
@@ -24,8 +23,8 @@ import java.util.Collections
interface BlockAccessor {
operator fun get(x: Int, y: Int, z: Int): Block?
operator fun set(x: Int, y: Int, z: Int, value: Block?)
operator fun get(x: Int, y: Int, z: Int): Int
operator fun set(x: Int, y: Int, z: Int, value: Int)
}
interface LightAccessor {
@@ -33,7 +32,7 @@ interface LightAccessor {
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 {
const val LENGTH = 16
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
var blocked = Array(LENGTH*LENGTH){1}
val blocked = Array(LENGTH*LENGTH){1}
var toBreak = false
for (y in LENGTH - 1 downTo 0) {
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 worldZ = z + chunk.coords.y * LENGTH
val light = sunLights[x, y, z]
if (blocks[x,y,z] != null) {
if (blocks[x,y,z] != 0) {
blocked[posInBlocked] = 0
if (blocked.sum()==0) {
toBreak = true
@@ -102,7 +101,7 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
if (newLightLevel <= 0) continue
// 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
// 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 {
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
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]
}
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
if (index < 0 || index >= blockData.size) return
blockData[index] = value
@@ -455,7 +454,7 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
val vertexCount = vaoSize / 7 // <-- changed
// Bind texture
glBindTexture(GL_TEXTURE_2D, Game.textureId)
glBindTexture(GL_TEXTURE_2D, Game.block_atlas.textureId)
glUniform1i(glGetUniformLocation(Game.shaderProgram, "texture1"), 0)
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) {
val y = sectionY + LENGTH * index - chunk.ySectionsOffset * 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 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,
x,
y,
z,
Game.block_atlas.getUV("crafting_table_top.png"),
chunk.coords,
1f,
"top"
)
if (chunk.world.blocks[worldX, y - 1, worldZ] == null) addFace(
if (chunk.world.blocks[worldX, y - 1, worldZ] == 0) addFace(
vertices,
x,
y,
z,
Game.block_atlas.getUV("oak_planks.png"),
chunk.coords,
1f,
"bottom"
)
if (chunk.world.blocks[worldX + 1, y, worldZ] == null) addFace(
if (chunk.world.blocks[worldX + 1, y, worldZ] == 0) addFace(
vertices,
x,
y,
z,
Game.block_atlas.getUV("crafting_table_side.png"),
chunk.coords,
1f,
"right"
)
if (chunk.world.blocks[worldX - 1, y, worldZ] == null) addFace(
if (chunk.world.blocks[worldX - 1, y, worldZ] == 0) addFace(
vertices,
x,
y,
z,
Game.block_atlas.getUV("crafting_table_side.png"),
chunk.coords,
1f,
"left"
)
if (chunk.world.blocks[worldX, y, worldZ + 1] == null) addFace(
if (chunk.world.blocks[worldX, y, worldZ + 1] == 0) addFace(
vertices,
x,
y,
z,
Game.block_atlas.getUV("crafting_table_front.png"),
chunk.coords,
1f,
"front"
)
if (chunk.world.blocks[worldX, y, worldZ - 1] == null) addFace(
if (chunk.world.blocks[worldX, y, worldZ - 1] == 0) addFace(
vertices,
x,
y,
z,
Game.block_atlas.getUV("crafting_table_front.png"),
chunk.coords,
1f,
"back"
@@ -562,7 +567,7 @@ class Chunk(
val coords: Vector2i,
val numberOfSections: Int,
val ySectionsOffset: Int,
var blockData: Array<Block?>?,
var blockData: Array<Int>?,
val peak: Int
) {
var sections: Array<ChunkSection> = Array(numberOfSections) { sectionIndex ->
@@ -628,12 +633,12 @@ class Chunk(
}
val blocks = object : BlockAccessor {
override operator fun get(x: Int, y: Int, z: Int): Block? {
if (!loaded) return null
return getChunkSection(y)?.blocks[x, floorMod(y, LENGTH), z]
override operator fun get(x: Int, y: Int, z: Int): Int {
if (!loaded) return 0
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
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 (z in minZ..maxZ) {
val block = world!!.blocks[x, y, z]
if (block != null) {
if (block != 0) {
boxes.add(
AABB(
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 {
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
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
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
include(":app")
include(":utils")
include("mc_terrain_java")
project(":mc_terrain_java").projectDir = file("lib/mc_terrain_java")
rootProject.name = "Buildtopia"