physics: add ray casting

This commit is contained in:
Lukas 2021-05-15 15:10:52 +02:00
parent 504185bee8
commit 49dcfdcc49
9 changed files with 182 additions and 24 deletions

View File

@ -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
}

View File

@ -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,
)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)))

View File

@ -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())

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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
}