diff --git a/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt b/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt index 9cab84cad..3cceaf2a8 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/entities/Entity.kt @@ -288,7 +288,7 @@ abstract class Entity( if (hasGravity && !isFlying) { newVelocity.y -= ProtocolDefinition.GRAVITY * deltaTime } - newVelocity *= 0.25f.pow(deltaTime) // apply + newVelocity *= 0.25f.pow(deltaTime) // apply friction if (newVelocity.length() < 0.05f) { newVelocity *= 0 } diff --git a/src/main/java/de/bixilon/minosoft/data/mappings/blocks/BlockState.kt b/src/main/java/de/bixilon/minosoft/data/mappings/blocks/BlockState.kt index fad623d83..e672fb4d3 100644 --- a/src/main/java/de/bixilon/minosoft/data/mappings/blocks/BlockState.kt +++ b/src/main/java/de/bixilon/minosoft/data/mappings/blocks/BlockState.kt @@ -39,6 +39,7 @@ data class BlockState( val tintColor: RGBColor? = null, val material: Material, val collisionShape: VoxelShape, + val occlusionShape: VoxelShape, ) { override fun hashCode(): Int { @@ -157,6 +158,14 @@ data class BlockState( VoxelShape.EMPTY } + val occlusion = data["occlusion_shapes"]?.let { + if (it.isJsonPrimitive) { + versionMapping.shapes[it.asInt] + } else { + VoxelShape(versionMapping.shapes, it) + } + } ?: VoxelShape.EMPTY + owner.renderOverride?.let { renderers.clear() renderers.addAll(it) @@ -169,6 +178,7 @@ data class BlockState( tintColor = tintColor, material = material, collisionShape = collision, + occlusionShape = occlusion, ) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/World.kt b/src/main/java/de/bixilon/minosoft/data/world/World.kt index b47cdee14..1dff8d1e9 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/World.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/World.kt @@ -20,10 +20,13 @@ import de.bixilon.minosoft.data.mappings.blocks.BlockState import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor import de.bixilon.minosoft.data.world.biome.accessor.NullBiomeAccessor import de.bixilon.minosoft.data.world.light.WorldLightAccessor +import de.bixilon.minosoft.gui.rendering.util.VecUtil import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition +import de.bixilon.minosoft.gui.rendering.util.VecUtil.floor import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkPosition import de.bixilon.minosoft.util.KUtil.synchronizedMapOf import glm_.vec2.Vec2i +import glm_.vec3.Vec3 import glm_.vec3.Vec3i /** @@ -110,4 +113,30 @@ class World : BiomeAccessor { return blocks.toMap() } + + data class RayCastHit( + val position: Vec3, + val distance: Float, + val i: Int, + ) + + fun raycast(origin: Vec3, direction: Vec3): RayCastHit { + val currentPosition = Vec3(origin) + fun getTotalDistance(): Float { + return (origin - currentPosition).length() + } + for (i in 0..MAX_STEPS) { + val blockPosition = currentPosition.floor + val distance = getBlockState(blockPosition)?.collisionShape?.plus(blockPosition)?.raycast(currentPosition, direction) ?: -1f + if (distance >= 0f) { + return RayCastHit(currentPosition + direction * distance, getTotalDistance() + distance, i) + } + currentPosition += direction * (VecUtil.getDistanceToNextIntegerAxis(currentPosition, direction) + 0.001) + } + return RayCastHit(currentPosition, getTotalDistance(), MAX_STEPS) + } + + companion object { + private const val MAX_STEPS = 100 + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/input/camera/Camera.kt b/src/main/java/de/bixilon/minosoft/gui/input/camera/Camera.kt index 7e7e7d821..c7e4b8612 100644 --- a/src/main/java/de/bixilon/minosoft/gui/input/camera/Camera.kt +++ b/src/main/java/de/bixilon/minosoft/gui/input/camera/Camera.kt @@ -103,20 +103,13 @@ class Camera( xOffset *= mouseSensitivity yOffset *= mouseSensitivity var yaw = xOffset.toFloat() + playerEntity.rotation.headYaw - var pitch = yOffset.toFloat() + playerEntity.rotation.pitch - - // make sure that when pitch is out of bounds, screen doesn't get flipped - if (pitch > 89.9) { - pitch = 89.9f - } else if (pitch < -89.9) { - pitch = -89.9f - } if (yaw > 180) { yaw -= 360 } else if (yaw < -180) { yaw += 360 } yaw %= 180 + val pitch = glm.clamp(yOffset.toFloat() + playerEntity.rotation.pitch, -89.9f, 89.9f) setRotation(yaw, pitch) } @@ -153,7 +146,7 @@ class Camera( movementFront.normalizeAssign() // when moving forwards, do not move down } if (renderWindow.inputHandler.isKeyBindingDown(KeyBindingsNames.MOVE_SPRINT)) { - cameraSpeed *= 5 + cameraSpeed *= PLAYER_SPRINT_SPEED_MODIFIER } if (ProtocolDefinition.FAST_MOVEMENT) { cameraSpeed *= 5 @@ -183,12 +176,10 @@ class Camera( if (renderWindow.inputHandler.isKeyBindingDown(KeyBindingsNames.MOVE_FLY_DOWN)) { deltaMovement -= CAMERA_UP_VEC3 * cameraSpeed } - } else { - if (playerEntity.onGround && renderWindow.inputHandler.isKeyBindingDown(KeyBindingsNames.MOVE_JUMP)) { - // TODO: jump delay, correct jump height - playerEntity.velocity.y += 0.75f * ProtocolDefinition.GRAVITY - playerEntity.onGround = false - } + } else if (playerEntity.onGround && renderWindow.inputHandler.isKeyBindingDown(KeyBindingsNames.MOVE_JUMP)) { + // TODO: jump delay, correct jump height + playerEntity.velocity.y += 0.75f * ProtocolDefinition.GRAVITY + playerEntity.onGround = false } if (deltaMovement != VecUtil.EMPTY_VEC3) { playerEntity.move(deltaMovement, false) @@ -227,7 +218,7 @@ class Camera( } private fun positionChangeCallback() { - blockPosition = (cameraPosition - Vec3(0, PLAYER_HEIGHT, 0)).blockPosition + blockPosition = playerEntity.position.blockPosition currentBiome = connection.world.getBiome(blockPosition) chunkPosition = blockPosition.chunkPosition sectionHeight = blockPosition.sectionHeight @@ -258,7 +249,7 @@ class Camera( } private fun getAbsoluteCameraPosition(): Vec3 { - return playerEntity.position + Vec3(0, PLAYER_HEIGHT, 0) + return playerEntity.position + Vec3(0, PLAYER_EYE_HEIGHT, 0) } fun checkPosition() { @@ -307,12 +298,13 @@ class Camera( } fun setPosition(position: Vec3) { - cameraPosition = (position + Vec3(0, PLAYER_HEIGHT, 0)) playerEntity.position = position + cameraPosition = getAbsoluteCameraPosition() } companion object { private val CAMERA_UP_VEC3 = Vec3(0.0f, 1.0f, 0.0f) - private const val PLAYER_HEIGHT = 1.3 // player is 1.8 blocks high, the camera is normally at 0.5. 1.8 - 0.5 = 1.13 + private const val PLAYER_EYE_HEIGHT = 1.3 // player is 1.8 blocks high, the camera is normally at 0.5. 1.8 - 0.5 = 1.13 + private const val PLAYER_SPRINT_SPEED_MODIFIER = 1.30000001192092896 } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/VoxelShape.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/VoxelShape.kt index 1a1db3d87..99b9c5d56 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/VoxelShape.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/VoxelShape.kt @@ -9,7 +9,7 @@ import de.bixilon.minosoft.gui.rendering.util.VecUtil import glm_.vec3.Vec3 import glm_.vec3.Vec3i -class VoxelShape(val aabbs: MutableList = mutableListOf()) { +class VoxelShape(private val aabbs: MutableList = mutableListOf()) { constructor(data: JsonElement, aabbs: List) : this() { when (data) { @@ -24,6 +24,20 @@ class VoxelShape(val aabbs: MutableList = mutableListOf()) { } } + // somehow, the kotlin compiler gives an error if both constructors have the "same" signature JsonElement, List<> + constructor(voxelShapes: List, data: JsonElement) : this() { + when (data) { + is JsonArray -> { + for (index in data) { + this.aabbs.addAll(voxelShapes[index.asInt].aabbs) + } + } + is JsonPrimitive -> { + this.aabbs.addAll(voxelShapes[data.asInt].aabbs) + } + } + } + fun intersect(other: AABB): Boolean { for (aabb in aabbs) { if (aabb.intersect(other)) { @@ -63,6 +77,19 @@ class VoxelShape(val aabbs: MutableList = mutableListOf()) { return result } + fun raycast(position: Vec3, direction: Vec3): Float { + for (aabb in aabbs) { + if (position in aabb) { + return 0f + } + val current = aabb.raycast(position, direction) + if (current >= -0.1f) { + return 0f + } + } + return -1f + } + companion object { val EMPTY = VoxelShape() val FULL = VoxelShape(mutableListOf(AABB(VecUtil.EMPTY_VEC3, VecUtil.ONES_VEC3))) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt index 8f670cd19..064ed238c 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/AABB.kt @@ -5,12 +5,15 @@ import com.google.gson.JsonElement import com.google.gson.JsonObject import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.gui.rendering.util.VecUtil -import de.bixilon.minosoft.gui.rendering.util.VecUtil.plus +import de.bixilon.minosoft.gui.rendering.util.VecUtil.choose +import de.bixilon.minosoft.gui.rendering.util.VecUtil.max +import de.bixilon.minosoft.gui.rendering.util.VecUtil.min import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3 import glm_.Java.Companion.glm import glm_.vec3.Vec3 import glm_.vec3.Vec3i + class AABB { val min: Vec3 val max: Vec3 @@ -43,7 +46,7 @@ class AABB { } operator fun plus(vec3i: Vec3i): AABB { - return AABB(vec3i plus min, vec3i plus max) + return plus(Vec3(vec3i)) } operator fun plus(other: AABB): AABB { @@ -147,6 +150,36 @@ class AABB { } } + fun raycast(position: Vec3, direction: Vec3): Float { + val tMins = getLengthMultipliers(position, direction, min) + val tMaxs = getLengthMultipliers(position, direction, max) + val tMin = tMins.max + val tMax = tMaxs.min + if (tMax < 0 || tMin > tMax) { + return -1f + } + return tMin + } + + private fun getLengthMultipliers(position: Vec3, direction: Vec3, target: Vec3): Vec3 { + return Vec3( + getLengthMultiplier(position, direction, target, Axes.X), + getLengthMultiplier(position, direction, target, Axes.Y), + getLengthMultiplier(position, direction, target, Axes.Z), + ) + } + + private fun getLengthMultiplier(position: Vec3, direction: Vec3, target: Vec3, axis: Axes): Float { + return (position.choose(axis) - target.choose(axis)) / direction.choose(axis) + } + + operator fun contains(position: Vec3): Boolean { + return ( + position.x in min.x..max.x && + position.y in min.y..max.y && + position.z in min.z..max.z ) + } + companion object { private fun getRange(min: Float, max: Float): IntRange { return IntRange(glm.floor(min).toInt(), glm.ceil(max).toInt()) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/nodes/debug/HUDSystemDebugNode.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/nodes/debug/HUDSystemDebugNode.kt index 75bd726fa..00ef2d084 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/nodes/debug/HUDSystemDebugNode.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/nodes/debug/HUDSystemDebugNode.kt @@ -20,6 +20,7 @@ import de.bixilon.minosoft.gui.rendering.hud.HUDRenderBuilder import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer import de.bixilon.minosoft.gui.rendering.hud.nodes.properties.NodeAlignment import de.bixilon.minosoft.gui.rendering.modding.events.ScreenResizeEvent +import de.bixilon.minosoft.gui.rendering.util.VecUtil.floor import de.bixilon.minosoft.modding.event.CallbackEventInvoker import de.bixilon.minosoft.modding.loading.ModLoader import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition @@ -67,6 +68,9 @@ class HUDSystemDebugNode(hudRenderer: HUDRenderer) : DebugScreenNode(hudRenderer text("Mods: ${ModLoader.MOD_MAP.size} active, ${hudRenderer.connection.eventListenerSize} listeners") } + private val targetPosition = text("TBA") + private val targetBlockState = text("TBA") + override fun init() { gpuText.sText = "GPU: " + (glGetString(GL_RENDERER) ?: "unknown") gpuVersionText.sText = "Version: " + (glGetString(GL_VERSION) ?: "unknown") @@ -82,7 +86,18 @@ class HUDSystemDebugNode(hudRenderer: HUDRenderer) : DebugScreenNode(hudRenderer } memoryText.sText = "Memory: ${getUsedMemoryPercent()}% ${getFormattedUsedMemory()}/${SystemInformation.MAX_MEMORY_TEXT}" allocatedMemoryText.sText = "Allocated: ${getAllocatedMemoryPercent()}% ${getFormattedAllocatedMemory()}" - + val rayCastHit = hudRenderer.connection.renderer?.renderWindow?.inputHandler?.camera?.let { + hudRenderer.connection.world.raycast(it.cameraPosition, it.cameraFront) + } + val position = rayCastHit?.position?.floor + val blockState = position?.let { hudRenderer.connection.world.getBlockState(it) } + if (rayCastHit?.distance ?: Float.MAX_VALUE < 5) { + targetPosition.sText = "looking at $position" + targetBlockState.sText = blockState.toString() + } else { + targetPosition.sText = "No blocks in reach!" + targetBlockState.sText = "" + } lastPrepareTime = System.currentTimeMillis() } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt index 25973c55a..39d23d02a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt @@ -24,8 +24,10 @@ import de.bixilon.minosoft.data.mappings.blocks.RandomOffsetTypes import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelElement import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import glm_.func.common.clamp +import glm_.func.common.floor import glm_.func.cos import glm_.func.sin +import glm_.glm import glm_.vec2.Vec2 import glm_.vec2.Vec2i import glm_.vec3.Vec3 @@ -212,4 +214,43 @@ object VecUtil { hash = hash * hash * 42317861L + hash * 11L return hash shr 16 } + + fun getDistanceToNextIntegerAxis(position: Vec3, direction: Vec3): Float { + val directionXDistance = (direction * getLengthMultiplier(direction, position, Axes.X)).length() + val directionYDistance = (direction * getLengthMultiplier(direction, position, Axes.Y)).length() + val directionZDistance = (direction * getLengthMultiplier(direction, position, Axes.Z)).length() + return glm.min(directionXDistance, directionYDistance, directionZDistance) + } + + private fun getLengthMultiplier(direction: Vec3, position: Vec3, axis: Axes): Float { + return (getTarget(direction, position, axis) - position.choose(axis)) / direction.choose(axis) + } + + private fun getTarget(direction: Vec3, position: Vec3, axis: Axes): Int { + return if (direction.choose(axis) > 0) { + position.floor.choose(axis) + 1 + } else { + position.floor.choose(axis) + } + } + + fun Vec3.choose(axis: Axes): Float { + return Axes.choose(axis, this) + } + + val Vec3.min: Float get() = glm.min(this.x, this.y, this.z) + + val Vec3.max: Float get() = glm.max(this.x, this.y, this.z) + + val Vec3.signs: Vec3 get() { + return Vec3(glm.sign(this.x), glm.sign(this.y), glm.sign(this.z)) + } + + val Vec3.floor: Vec3i get() { + return Vec3i(this.x.floor, this.y.floor, this.z.floor) + } + + fun Vec3i.choose(axis: Axes): Int { + return Axes.choose(axis, this) + } } diff --git a/src/main/java/de/bixilon/minosoft/util/MMath.kt b/src/main/java/de/bixilon/minosoft/util/MMath.kt index 6697b3a45..aa608dc51 100644 --- a/src/main/java/de/bixilon/minosoft/util/MMath.kt +++ b/src/main/java/de/bixilon/minosoft/util/MMath.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.util +import glm_.glm import glm_.vec2.Vec2i import kotlin.math.floor @@ -73,6 +74,8 @@ object MMath { return ((value * 10).toInt() + 5) / 10 } + val Float.round10: Float get() = (this * 10).toInt().toFloat() / 10f + fun round10Up(value: Float): Int { val intValue = value.toInt() val rest = value / intValue @@ -89,4 +92,12 @@ object MMath { fun fractionalPart(value: Double): Double { return value - floor(value) } + + fun linearInterpolate(delta: Float, start: Float, end: Float): Float { + return start + delta * (end - start) + } + + val Float.floor: Float get() = glm.floor(this) + + val Float.fractionalPart: Float get() = this - floor }