mirror of
https://github.com/Ugric/Buildtopia.git
synced 2026-10-28 09:43:46 +00:00
add player movement and physics based on blocks around the player
This commit is contained in:
1 parent
7ae3551ddd
commit
dcddc3c944
7 files changed
+264
-146
No files matched your search
@@ -33,12 +33,11 @@ fun createShader(vertexSrc: String, fragmentSrc: String): Int {
|
||||
return program
|
||||
}
|
||||
|
||||
fun getCameraMatrix(position: Vector3d, pitch: Double, yaw: Double, roll: Double): Matrix4d {
|
||||
val view = Matrix4d()
|
||||
fun getCameraRotationMatrix(pitch: Float, yaw: Float, roll: Float): Matrix4f {
|
||||
val view = Matrix4f()
|
||||
view.identity().rotateX(-pitch) // pitch = X
|
||||
.rotateY(-yaw) // yaw = Y
|
||||
.rotateZ(-roll) // roll = Z
|
||||
.translate(-position.x, -position.y, -position.z) // move world opposite to camera
|
||||
.rotateZ(-roll) // move world opposite to camera
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
@@ -9,45 +9,12 @@ import dev.wbell.buildtopia.app.loadResource
|
||||
import dev.wbell.buildtopia.app.updateProjection
|
||||
import org.joml.Vector3d
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MAJOR
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MINOR
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CURSOR
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED
|
||||
import org.lwjgl.glfw.GLFW.GLFW_CURSOR_NORMAL
|
||||
import org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE
|
||||
import org.lwjgl.glfw.GLFW.GLFW_OPENGL_CORE_PROFILE
|
||||
import org.lwjgl.glfw.GLFW.GLFW_OPENGL_PROFILE
|
||||
import org.lwjgl.glfw.GLFW.GLFW_PRESS
|
||||
import org.lwjgl.glfw.GLFW.glfwCreateWindow
|
||||
import org.lwjgl.glfw.GLFW.glfwDestroyWindow
|
||||
import org.lwjgl.glfw.GLFW.glfwInit
|
||||
import org.lwjgl.glfw.GLFW.glfwMakeContextCurrent
|
||||
import org.lwjgl.glfw.GLFW.glfwSetCursorPos
|
||||
import org.lwjgl.glfw.GLFW.glfwSetCursorPosCallback
|
||||
import org.lwjgl.glfw.GLFW.glfwSetFramebufferSizeCallback
|
||||
import org.lwjgl.glfw.GLFW.glfwSetInputMode
|
||||
import org.lwjgl.glfw.GLFW.glfwSetKeyCallback
|
||||
import org.lwjgl.glfw.GLFW.glfwTerminate
|
||||
import org.lwjgl.glfw.GLFW.glfwWindowHint
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
import org.lwjgl.opengl.GL
|
||||
import org.lwjgl.opengl.GL11.GL_DEPTH_TEST
|
||||
import org.lwjgl.opengl.GL11.glEnable
|
||||
import org.lwjgl.opengl.GL20.glGetUniformLocation
|
||||
import org.lwjgl.opengl.GL20.glUseProgram
|
||||
import org.lwjgl.system.MemoryUtil.NULL
|
||||
import org.lwjgl.opengl.GL11.GL_NEAREST
|
||||
import org.lwjgl.opengl.GL11.GL_REPEAT
|
||||
import org.lwjgl.opengl.GL11.GL_RGBA
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_2D
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S
|
||||
import org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T
|
||||
import org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE
|
||||
import org.lwjgl.opengl.GL11.glBindTexture
|
||||
import org.lwjgl.opengl.GL11.glGenTextures
|
||||
import org.lwjgl.opengl.GL11.glTexImage2D
|
||||
import org.lwjgl.opengl.GL11.glTexParameteri
|
||||
import org.lwjgl.opengl.GL13.*
|
||||
import org.lwjgl.stb.STBImage
|
||||
import java.nio.IntBuffer
|
||||
import kotlin.math.PI
|
||||
@@ -111,17 +78,19 @@ object Game {
|
||||
fun init() {
|
||||
// Initialize GLFW
|
||||
if (!glfwInit()) throw RuntimeException("Failed to initialize GLFW")
|
||||
glfwWindowHint(GLFW_SAMPLES, 4)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3)
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
|
||||
window = glfwCreateWindow(width, height, "Blocktopia", NULL, NULL)
|
||||
if (window == NULL) throw RuntimeException("Failed to create GLFW window")
|
||||
|
||||
glfwSwapInterval(1)
|
||||
window?.let { window ->
|
||||
glfwMakeContextCurrent(window)
|
||||
GL.createCapabilities()
|
||||
glEnable(GL_MULTISAMPLE)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
session.World = World(Player(Vector3d(0.0, 4.0, 0.0), 0.0,0.0), session)
|
||||
session.World = World(Player(Vector3d(0.0, 100.0, 0.0), 0f,0f), session)
|
||||
session.World!!.player.world = session.World
|
||||
|
||||
textureId = loadTexture("/resource_pack/textures/block/cobblestone.png")
|
||||
@@ -171,10 +140,10 @@ object Game {
|
||||
if (cursorDisabled && Cursor.lastX != null && Cursor.lastY != null) {
|
||||
val dx = (xpos - Cursor.lastX!!) / width
|
||||
val dy = (ypos - Cursor.lastY!!) / height
|
||||
session.World?.player?.yaw -= dx * 5f
|
||||
session.World?.player?.pitch -= dy * 5f
|
||||
session.World?.player?.yaw -= dx.toFloat() * 3.5f
|
||||
session.World?.player?.pitch -= dy.toFloat() * 3.5f
|
||||
session.World?.player?.pitch?.let {
|
||||
session.World?.player?.pitch = it.coerceIn(-PI / 2, PI / 2)
|
||||
session.World?.player?.pitch = it.coerceIn(-PI.toFloat() / 2, PI.toFloat() / 2)
|
||||
}
|
||||
}
|
||||
Cursor.lastX = xpos
|
||||
|
||||
@@ -4,8 +4,8 @@ import org.joml.Vector3d
|
||||
|
||||
class Camera(
|
||||
val position: Vector3d,
|
||||
var pitch: Double,
|
||||
var yaw: Double,
|
||||
var roll: Double
|
||||
var pitch: Float,
|
||||
var yaw: Float,
|
||||
var roll: Float
|
||||
) {
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import dev.wbell.buildtopia.app.game.session.world.chunk.ChunkSection
|
||||
import dev.wbell.buildtopia.app.game.session.world.player.Player
|
||||
import dev.wbell.buildtopia.app.game.settings.SettingKey
|
||||
import dev.wbell.buildtopia.app.game.settings.Settings
|
||||
import dev.wbell.buildtopia.app.getCameraMatrix
|
||||
import dev.wbell.buildtopia.app.getCameraRotationMatrix
|
||||
import org.joml.Vector2i
|
||||
import org.joml.Vector3d
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
@@ -20,6 +20,8 @@ import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
fun floorDiv(a: Int, b: Int): Int {
|
||||
// rounds down (towards -∞), unlike / which rounds toward 0
|
||||
@@ -45,17 +47,22 @@ class World(val player: Player, val session: Session) {
|
||||
var lastTick = glfwGetTime()
|
||||
|
||||
init {
|
||||
val centerChunk = Vector2i(
|
||||
(player.position.x / ChunkSection.LENGTH).toInt(),
|
||||
(player.position.z / ChunkSection.LENGTH).toInt()
|
||||
)
|
||||
val renderDistance = Settings.get(SettingKey.RENDERDISTANCE, 32)!!
|
||||
for (x in -renderDistance..<renderDistance) {
|
||||
for (z in -renderDistance..<renderDistance) {
|
||||
chunks[packChunkKey(x, z)] =
|
||||
val size = (16*16)*(64)
|
||||
chunks[packChunkKey(centerChunk.x+x, centerChunk.y+z)] =
|
||||
Chunk(
|
||||
this,
|
||||
Vector2i(x, z),
|
||||
Vector2i(centerChunk.x+x, centerChunk.y+z),
|
||||
24,
|
||||
4,
|
||||
Array(ChunkSection.LENGTH * ChunkSection.LENGTH * ChunkSection.LENGTH * 24) {
|
||||
if (it < 34000) Block() else null
|
||||
if (it < size+(16*16*Random.nextInt(0,2))) Block() else null
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -77,7 +84,7 @@ class World(val player: Player, val session: Session) {
|
||||
for (dx in -renderDistance - 1..renderDistance) {
|
||||
for (dz in -renderDistance - 1..renderDistance) {
|
||||
val chunk = getChunk(centerChunk.x + dx, centerChunk.y + dz)
|
||||
if (chunk != null && chunk.updateChunk) {
|
||||
if (chunk != null) {
|
||||
val dist2 = dx * dx + dz * dz
|
||||
chunksToUpdate.add(chunk to dist2)
|
||||
}
|
||||
@@ -87,8 +94,7 @@ class World(val player: Player, val session: Session) {
|
||||
// Sort by distance
|
||||
chunksToUpdate.sortBy { it.second }
|
||||
for ((chunk, _) in chunksToUpdate) {
|
||||
chunk.renderMesh() // background mesh generation
|
||||
chunkMeshQueue.add { chunk.uploadChunkMesh() } // main thread upload
|
||||
if (chunk.renderMesh()) chunkMeshQueue.add { chunk.uploadChunkMesh() } // main thread upload
|
||||
}
|
||||
delay(tickTime)
|
||||
}
|
||||
@@ -106,7 +112,7 @@ class World(val player: Player, val session: Session) {
|
||||
}
|
||||
}
|
||||
|
||||
var camera: Camera = Camera(Vector3d(0.0, 0.0, 0.0), 0.0, 0.0, 0.0)
|
||||
var camera: Camera = Camera(Vector3d(0.0, 0.0, 0.0), 0f, 0f, 0f)
|
||||
|
||||
private fun packChunkKey(x: Int, z: Int): Long =
|
||||
x.toLong() and 0xFFFFFFFFL or ((z.toLong() and 0xFFFFFFFFL) shl 32)
|
||||
@@ -146,7 +152,7 @@ class World(val player: Player, val session: Session) {
|
||||
while (chunkMeshQueue.isNotEmpty()) {
|
||||
chunkMeshQueue.poll()?.invoke()
|
||||
}
|
||||
val deltaTickTime = ((glfwGetTime() - lastTick) / 0.05).coerceIn(0.0, 1.0)
|
||||
val alpha = ((glfwGetTime() - lastTick) / 0.05).coerceIn(0.0, 1.0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glClearColor(0f, 0.6f, 1f, 1f)
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
@@ -161,9 +167,9 @@ class World(val player: Player, val session: Session) {
|
||||
player.position.y -= deltaTime * 10
|
||||
}
|
||||
|
||||
camera = player.getCamera(deltaTickTime)
|
||||
camera = player.getCamera(alpha)
|
||||
|
||||
val view = getCameraMatrix(camera.position, camera.pitch, camera.yaw, camera.roll)
|
||||
val view = getCameraRotationMatrix(camera.pitch, camera.yaw, camera.roll)
|
||||
val viewBuffer = FloatArray(16)
|
||||
view.get(viewBuffer)
|
||||
glUniformMatrix4fv(Game.viewLoc, false, viewBuffer)
|
||||
@@ -176,7 +182,7 @@ class World(val player: Player, val session: Session) {
|
||||
for (dx in -renderDistance - 1..renderDistance) {
|
||||
for (dz in -renderDistance - 1..renderDistance) {
|
||||
val chunk = getChunk(centerChunk.x + dx, centerChunk.y + dz)
|
||||
chunk?.render(deltaTickTime)
|
||||
chunk?.render(alpha)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,5 +63,7 @@ fun addFace(vertices: MutableList<Float>, x: Int, y: Int, z: Int, cubeScale: Flo
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
|
||||
class Block {
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import org.joml.Vector2d
|
||||
import org.joml.Vector2i
|
||||
import org.joml.Vector3d
|
||||
import org.joml.Vector3f
|
||||
import org.lwjgl.glfw.GLFW.glfwGetTime
|
||||
import org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray
|
||||
import org.lwjgl.opengl.ARBVertexArrayObject.glGenVertexArrays
|
||||
import org.lwjgl.opengl.GL11.GL_FLOAT
|
||||
@@ -24,6 +25,7 @@ import org.lwjgl.opengl.GL15.glGenBuffers
|
||||
import org.lwjgl.opengl.GL20.glEnableVertexAttribArray
|
||||
import org.lwjgl.opengl.GL20.glVertexAttribPointer
|
||||
import org.lwjgl.opengl.GL30.*
|
||||
import kotlin.div
|
||||
|
||||
|
||||
interface BlockAccessor {
|
||||
@@ -48,13 +50,90 @@ class ChunkSection(val blockData: Array<Block?>, val chunk: Chunk, val index: In
|
||||
val index = x + z * LENGTH + y * LENGTH * LENGTH
|
||||
if (index < 0 || index >= blockData.size) return
|
||||
blockData[index] = value
|
||||
if (x == 0) chunk.world.getChunk(chunk.coords.x - 1, chunk.coords.y)?.updateChunk = true
|
||||
if (x == LENGTH - 1) chunk.world.getChunk(chunk.coords.x + 1, chunk.coords.y)?.updateChunk = true
|
||||
if (z == 0) chunk.world.getChunk(chunk.coords.x, chunk.coords.y - 1)?.updateChunk = true
|
||||
if (z == LENGTH - 1) chunk.world.getChunk(chunk.coords.x, chunk.coords.y + 1)?.updateChunk = true
|
||||
// if (x == 0) chunk.world.getChunk(chunk.coords.x - 1, chunk.coords.y)?.updateChunk = true
|
||||
// if (x == LENGTH - 1) chunk.world.getChunk(chunk.coords.x + 1, chunk.coords.y)?.updateChunk = true
|
||||
// if (z == 0) chunk.world.getChunk(chunk.coords.x, chunk.coords.y - 1)?.updateChunk = true
|
||||
// if (z == LENGTH - 1) chunk.world.getChunk(chunk.coords.x, chunk.coords.y + 1)?.updateChunk = true
|
||||
}
|
||||
}
|
||||
|
||||
var meshExists: Boolean = false
|
||||
var vao: Int = 0
|
||||
var vaoSize: Int = 0
|
||||
var vbo: Int = 0
|
||||
var timeMeshCreated = 0.0
|
||||
var updateMesh: Boolean = true
|
||||
var verticesForUpload:MutableList<Float>? = null
|
||||
|
||||
fun renderMesh(): Boolean {
|
||||
if (!updateMesh) return false
|
||||
verticesForUpload = mutableListOf<Float>()
|
||||
setVertices(verticesForUpload!!)
|
||||
updateMesh = false
|
||||
return true
|
||||
}
|
||||
|
||||
fun uploadChunkMesh() {
|
||||
if (verticesForUpload == null) return
|
||||
if (meshExists) {
|
||||
glDeleteBuffers(vbo)
|
||||
glDeleteVertexArrays(vao)
|
||||
}
|
||||
if (verticesForUpload!!.isEmpty()) {
|
||||
meshExists = false
|
||||
updateMesh = false
|
||||
verticesForUpload = null
|
||||
timeMeshCreated = glfwGetTime()
|
||||
this.vao = 0
|
||||
this.vbo = 0
|
||||
return
|
||||
}
|
||||
this.vao = glGenVertexArrays()
|
||||
this.vbo = glGenBuffers()
|
||||
glBindVertexArray(vao)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
||||
glBufferData(GL_ARRAY_BUFFER, verticesForUpload!!.toFloatArray(), GL_STATIC_DRAW)
|
||||
|
||||
val stride = 5 * 4 // 5 floats * 4 bytes
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0) // position
|
||||
glEnableVertexAttribArray(0)
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 3 * 4) // tex coords
|
||||
glEnableVertexAttribArray(1)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
glBindVertexArray(0)
|
||||
|
||||
this.vaoSize = verticesForUpload!!.size;
|
||||
meshExists = true
|
||||
timeMeshCreated = glfwGetTime()
|
||||
verticesForUpload = null
|
||||
}
|
||||
|
||||
fun render(alpha: Double) {
|
||||
// Draw the grid
|
||||
if (!meshExists) return
|
||||
val vertexCount = vaoSize / 5
|
||||
|
||||
// Normal cube
|
||||
glBindTexture(GL_TEXTURE_2D, Game.textureId)
|
||||
glUniform1i(glGetUniformLocation(Game.shaderProgram, "texture1"), 0)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
||||
val model = Matrix4f().identity().translate(
|
||||
Vector3f(
|
||||
(chunk.coords.x * LENGTH-chunk.world.camera.position.x).toFloat(),
|
||||
-chunk.world.camera.position.y.toFloat(),
|
||||
(chunk.coords.y * LENGTH-chunk.world.camera.position.z).toFloat()
|
||||
)
|
||||
) // adjust position/scale if needed
|
||||
val modelBuffer = FloatArray(16)
|
||||
model.get(modelBuffer)
|
||||
glUniformMatrix4fv(Game.modelLoc, false, modelBuffer)
|
||||
|
||||
glBindVertexArray(vao)
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount)
|
||||
glBindVertexArray(0)
|
||||
}
|
||||
|
||||
fun setVertices(vertices: MutableList<Float>) {
|
||||
for (x in 0..<LENGTH) {
|
||||
for (sectionY in 0..<LENGTH) {
|
||||
@@ -104,20 +183,13 @@ class Chunk(
|
||||
}
|
||||
}
|
||||
|
||||
private var meshExists: Boolean = false
|
||||
var vao: Int = 0
|
||||
var vaoSize: Int = 0
|
||||
var vbo: Int = 0
|
||||
var updateChunk: Boolean = true
|
||||
var verticesForUpload:MutableList<Float>? = null
|
||||
|
||||
|
||||
fun renderMesh() {
|
||||
verticesForUpload = mutableListOf<Float>()
|
||||
for (i in 0..<numberOfSections) {
|
||||
sections[i].setVertices(verticesForUpload!!)
|
||||
fun renderMesh(): Boolean {
|
||||
var hasRendered = false
|
||||
for (i in 0..<numberOfSections){
|
||||
if (sections[i].renderMesh()) hasRendered = true
|
||||
}
|
||||
updateChunk = false
|
||||
return hasRendered
|
||||
// if (meshExists) {
|
||||
// glDeleteBuffers(vbo)
|
||||
// glDeleteVertexArrays(vao)
|
||||
@@ -142,58 +214,15 @@ class Chunk(
|
||||
}
|
||||
|
||||
fun uploadChunkMesh() {
|
||||
if (meshExists) {
|
||||
glDeleteBuffers(vbo)
|
||||
glDeleteVertexArrays(vao)
|
||||
for (i in 0..<numberOfSections){
|
||||
sections[i].uploadChunkMesh()
|
||||
}
|
||||
this.vao = glGenVertexArrays()
|
||||
this.vbo = glGenBuffers()
|
||||
glBindVertexArray(vao)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
||||
glBufferData(GL_ARRAY_BUFFER, verticesForUpload!!.toFloatArray(), GL_STATIC_DRAW)
|
||||
|
||||
val stride = 5 * 4 // 5 floats * 4 bytes
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0) // position
|
||||
glEnableVertexAttribArray(0)
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 3 * 4) // tex coords
|
||||
glEnableVertexAttribArray(1)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
glBindVertexArray(0)
|
||||
|
||||
this.vaoSize = verticesForUpload!!.size;
|
||||
meshExists = true
|
||||
verticesForUpload = null
|
||||
}
|
||||
|
||||
|
||||
fun render(deltaTime: Double) {
|
||||
|
||||
// Upload to VBO/VAO
|
||||
|
||||
|
||||
if (meshExists) {
|
||||
// Draw the grid
|
||||
val vertexCount = vaoSize / 5
|
||||
|
||||
// Normal cube
|
||||
glBindTexture(GL_TEXTURE_2D, Game.textureId)
|
||||
glUniform1i(glGetUniformLocation(Game.shaderProgram, "texture1"), 0)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
||||
val model = Matrix4f().identity().translate(
|
||||
Vector3f(
|
||||
(coords.x * ChunkSection.LENGTH).toFloat(),
|
||||
-(ChunkSection.LENGTH * ySectionsOffset).toFloat(),
|
||||
(coords.y * ChunkSection.LENGTH).toFloat()
|
||||
)
|
||||
) // adjust position/scale if needed
|
||||
val modelBuffer = FloatArray(16)
|
||||
model.get(modelBuffer)
|
||||
glUniformMatrix4fv(Game.modelLoc, false, modelBuffer)
|
||||
|
||||
glBindVertexArray(vao)
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount)
|
||||
glBindVertexArray(0)
|
||||
fun render(alpha: Double) {
|
||||
for (i in 0..<numberOfSections){
|
||||
sections[i].render(alpha)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,32 @@ import dev.wbell.buildtopia.app.game.Game
|
||||
import dev.wbell.buildtopia.app.game.camera.Camera
|
||||
import dev.wbell.buildtopia.app.game.session.world.World
|
||||
import org.joml.Vector2d
|
||||
import org.joml.Vector2f
|
||||
import org.joml.Vector3d
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
import org.lwjgl.glfw.GLFW.glfwGetKey
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
|
||||
class Player(val position: Vector3d, var pitch: Double, var yaw: Double) {
|
||||
|
||||
data class AABB(
|
||||
var minX: Double, var minY: Double, var minZ: Double,
|
||||
var maxX: Double, var maxY: Double, var maxZ: Double
|
||||
) {
|
||||
fun offset(dx: Double, dy: Double, dz: Double) =
|
||||
AABB(minX + dx, minY + dy, minZ + dz, maxX + dx, maxY + dy, maxZ + dz)
|
||||
|
||||
fun intersects(other: AABB): Boolean =
|
||||
maxX > other.minX && minX < other.maxX &&
|
||||
maxY > other.minY && minY < other.maxY &&
|
||||
maxZ > other.minZ && minZ < other.maxZ
|
||||
}
|
||||
|
||||
class Player(val position: Vector3d, var pitch: Float, var yaw: Float) {
|
||||
var world: World? = null
|
||||
val velocity = Vector3d(0.0, 0.0, 0.0)
|
||||
|
||||
@@ -20,34 +38,63 @@ class Player(val position: Vector3d, var pitch: Double, var yaw: Double) {
|
||||
// Store last and current positions for interpolation
|
||||
private val lastPosition = Vector3d(position)
|
||||
|
||||
|
||||
val width = 0.6
|
||||
val height = 1.8
|
||||
fun getBoundingBox(): AABB {
|
||||
return AABB(
|
||||
position.x-width/2, position.y,
|
||||
position.z-width/2, position.x + width/2,
|
||||
position.y + height, position.z + width/2
|
||||
)
|
||||
}
|
||||
|
||||
fun getNearbyBlockBoxes(bb: AABB): List<AABB> {
|
||||
val boxes = mutableListOf<AABB>()
|
||||
|
||||
val minX = floor(bb.minX - 0.5).toInt()
|
||||
val minY = floor(bb.minY).toInt()
|
||||
val minZ = floor(bb.minZ - 0.5).toInt()
|
||||
val maxX = floor(bb.maxX + 0.5).toInt()
|
||||
val maxY = floor(bb.maxY).toInt()
|
||||
val maxZ = floor(bb.maxZ + 0.5).toInt()
|
||||
|
||||
for (x in minX..maxX) {
|
||||
for (y in minY..maxY) {
|
||||
for (z in minZ..maxZ) {
|
||||
val block = world!!.blocks[x, y, z]
|
||||
if (block != null) {
|
||||
boxes.add(AABB(
|
||||
x - 0.5, y.toDouble(), z - 0.5,
|
||||
x + 0.5, y + 1.0, z + 0.5
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return boxes
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
lastPosition.set(position)
|
||||
val blockUnderneath = world!!.blocks[position.x.toInt(),position.y.toInt()-2,position.z.toInt()]
|
||||
isOnGround = position.y + velocity.y <= 4
|
||||
|
||||
velocity.y -= 0.08
|
||||
if (position.y + velocity.y <= 4) {
|
||||
position.y = 4.0
|
||||
velocity.y = 0.0
|
||||
}
|
||||
if (isOnGround&&glfwGetKey(Game.window!!, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
velocity.y = 0.42
|
||||
}
|
||||
println(position.y)
|
||||
velocity.y = (velocity.y-0.08)*0.98
|
||||
val slipperiness = if (isOnGround) 0.6 else 1.0
|
||||
val forward = Vector2d(-sin(yaw), -cos(yaw))
|
||||
val left = Vector2d(-cos(yaw), sin(yaw))
|
||||
val movement = Vector2d(0.0,0.0)
|
||||
val forward = Vector2f(-sin(yaw), -cos(yaw))
|
||||
val left = Vector2f(-cos(yaw), sin(yaw))
|
||||
val movement = Vector2d(0.0, 0.0)
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_W) == GLFW_PRESS) {
|
||||
movement.x=1.0
|
||||
movement.x = 1.0
|
||||
}
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_S) == GLFW_PRESS) {
|
||||
movement.x=-1.0
|
||||
movement.x = -1.0
|
||||
}
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_A) == GLFW_PRESS) {
|
||||
movement.y=1.0
|
||||
movement.y = 1.0
|
||||
}
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_D) == GLFW_PRESS) {
|
||||
movement.y=-1.0
|
||||
movement.y = -1.0
|
||||
}
|
||||
if (movement.lengthSquared() != 0.0) {
|
||||
movement.normalize()
|
||||
@@ -56,9 +103,75 @@ class Player(val position: Vector3d, var pitch: Double, var yaw: Double) {
|
||||
if (glfwGetKey(Game.window!!, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) {
|
||||
movement.mul(1.3)
|
||||
}
|
||||
velocity.x = velocity.x*slipperiness*0.91+movement.x*(0.6/slipperiness).pow(3)*forward.x+movement.y*(0.6/slipperiness).pow(3)*left.x
|
||||
velocity.z = velocity.z*slipperiness*0.91+movement.x*(0.6/slipperiness).pow(3)*forward.y+movement.y*(0.6/slipperiness).pow(3)*left.y
|
||||
position.add(velocity)
|
||||
velocity.x =
|
||||
velocity.x * slipperiness * 0.91 + movement.x * (0.6 / slipperiness).pow(3) * forward.x + movement.y * (0.6 / slipperiness).pow(
|
||||
3
|
||||
) * left.x
|
||||
velocity.z =
|
||||
velocity.z * slipperiness * 0.91 + movement.x * (0.6 / slipperiness).pow(3) * forward.y + movement.y * (0.6 / slipperiness).pow(
|
||||
3
|
||||
) * left.y
|
||||
if (isOnGround && glfwGetKey(Game.window!!, GLFW_KEY_SPACE) == GLFW_PRESS) {
|
||||
velocity.y = 0.42
|
||||
}
|
||||
moveAxis(velocity.x, 0.0, 0.0) // move along X
|
||||
moveAxis(0.0, velocity.y, 0.0) // move along Y
|
||||
moveAxis(0.0, 0.0, velocity.z) // move along Z
|
||||
println(velocity)
|
||||
}
|
||||
|
||||
|
||||
private fun moveAxis(dx: Double, dy: Double, dz: Double) {
|
||||
val steps = ceil(maxOf(abs(dx), abs(dy), abs(dz))).toInt().coerceAtLeast(1)
|
||||
val stepX = dx / steps
|
||||
val stepY = dy / steps
|
||||
val stepZ = dz / steps
|
||||
|
||||
if (stepY != 0.0) isOnGround = false
|
||||
|
||||
repeat(steps) {
|
||||
val bb = getBoundingBox().offset(stepX, stepY, stepZ)
|
||||
val colliders = getNearbyBlockBoxes(bb)
|
||||
|
||||
var collidedX = false
|
||||
var collidedY = false
|
||||
var collidedZ = false
|
||||
|
||||
for (blockBB in colliders) {
|
||||
if (bb.intersects(blockBB)) {
|
||||
// Y-axis collision
|
||||
if (stepY < 0.0) { // moving down
|
||||
position.y = blockBB.maxY
|
||||
velocity.y = 0.0
|
||||
collidedY = true
|
||||
isOnGround = true
|
||||
} else if (stepY > 0.0) { // moving up
|
||||
position.y = blockBB.minY - height
|
||||
velocity.y = 0.0
|
||||
collidedY = true
|
||||
}
|
||||
|
||||
// X-axis collision
|
||||
if (stepX != 0.0) {
|
||||
position.x = if (stepX > 0) blockBB.minX - width/2 else blockBB.maxX + width/2
|
||||
velocity.x = 0.0
|
||||
collidedX = true
|
||||
}
|
||||
|
||||
// Z-axis collision
|
||||
if (stepZ != 0.0) {
|
||||
position.z = if (stepZ > 0) blockBB.minZ - width/2 else blockBB.maxZ + width/2
|
||||
velocity.z = 0.0
|
||||
collidedZ = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no collision on a given axis, move normally
|
||||
if (!collidedX) position.x += stepX
|
||||
if (!collidedY) position.y += stepY
|
||||
if (!collidedZ) position.z += stepZ
|
||||
}
|
||||
}
|
||||
|
||||
fun getCamera(alpha: Double): Camera {
|
||||
@@ -67,7 +180,7 @@ class Player(val position: Vector3d, var pitch: Double, var yaw: Double) {
|
||||
lerp(lastPosition.y, position.y, alpha),
|
||||
lerp(lastPosition.z, position.z, alpha)
|
||||
)
|
||||
return Camera(interpPos.add(0.0, 1.0, 0.0), pitch, yaw, 0.0)
|
||||
return Camera(interpPos.add(0.0, 1.0, 0.0), pitch, yaw, 0f)
|
||||
}
|
||||
|
||||
private fun lerp(a: Double, b: Double, t: Double): Double {
|
||||
|
||||
Reference in new issue
Block a user