mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-14 09:56:37 -04:00
physics: add ray casting
This commit is contained in:
parent
504185bee8
commit
49dcfdcc49
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,13 +176,11 @@ 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)) {
|
||||
} 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)
|
||||
recalculateViewProjectionMatrix()
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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<AABB> = mutableListOf()) {
|
||||
class VoxelShape(private val aabbs: MutableList<AABB> = mutableListOf()) {
|
||||
|
||||
constructor(data: JsonElement, aabbs: List<AABB>) : this() {
|
||||
when (data) {
|
||||
@ -24,6 +24,20 @@ class VoxelShape(val aabbs: MutableList<AABB> = mutableListOf()) {
|
||||
}
|
||||
}
|
||||
|
||||
// somehow, the kotlin compiler gives an error if both constructors have the "same" signature JsonElement, List<>
|
||||
constructor(voxelShapes: List<VoxelShape>, 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<AABB> = 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)))
|
||||
|
@ -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())
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user