mirror of
https://github.com/Ugric/Buildtopia.git
synced 2026-10-28 09:43:46 +00:00
add texture atlas and start setting up server system
This commit is contained in:
1 parent
dba91b8476
commit
71457794be
18 files changed
+444
-159
No files matched your search
Generated
+1
@@ -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>
|
||||
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
Generated
+1
@@ -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>
|
||||
Generated
+36
-5
@@ -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 @@
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"kotlin-language-version-configured": "true"
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "/home/william/Documents/GitHub/mc_terrain_java"
|
||||
}
|
||||
}</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>
|
||||
@@ -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,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
|
||||
//
|
||||
//
|
||||
|
||||
+49
@@ -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) }
|
||||
}
|
||||
}
|
||||
+19
@@ -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))//
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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()
|
||||
@@ -98,51 +122,52 @@ class World(val player: Player, val session: Session) {
|
||||
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 ->
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
Reference in new issue
Block a user