physics: improve raycasting

This commit is contained in:
Lukas 2021-05-24 18:01:20 +02:00
parent 21a7d6856c
commit 52f873c5f8
7 changed files with 112 additions and 105 deletions

View File

@ -15,6 +15,7 @@ package de.bixilon.minosoft.data
import de.bixilon.minosoft.data.mappings.blocks.properties.serializer.BlockPropertiesSerializer import de.bixilon.minosoft.data.mappings.blocks.properties.serializer.BlockPropertiesSerializer
import de.bixilon.minosoft.gui.rendering.chunk.models.FaceSize import de.bixilon.minosoft.gui.rendering.chunk.models.FaceSize
import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelElement import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelElement
import de.bixilon.minosoft.gui.rendering.util.VecUtil.get
import de.bixilon.minosoft.util.KUtil import de.bixilon.minosoft.util.KUtil
import de.bixilon.minosoft.util.enum.ValuesEnum import de.bixilon.minosoft.util.enum.ValuesEnum
import glm_.vec2.Vec2i import glm_.vec2.Vec2i
@ -87,12 +88,15 @@ enum class Directions(val directionVector: Vec3i) {
return false return false
} }
operator fun get(axis: Axes): Int {
return directionVector[axis]
}
companion object : BlockPropertiesSerializer, ValuesEnum<Directions> { companion object : BlockPropertiesSerializer, ValuesEnum<Directions> {
override val VALUES = values() override val VALUES = values()
override val NAME_MAP: Map<String, Directions> = KUtil.getEnumValues(VALUES) override val NAME_MAP: Map<String, Directions> = KUtil.getEnumValues(VALUES)
val SIDES = arrayOf(NORTH, SOUTH, WEST, EAST) val SIDES = arrayOf(NORTH, SOUTH, WEST, EAST)
const val SIDES_OFFSET = 2
override fun deserialize(value: Any): Directions { override fun deserialize(value: Any): Directions {
return NAME_MAP[value] ?: throw IllegalArgumentException("No such property: $value") return NAME_MAP[value] ?: throw IllegalArgumentException("No such property: $value")

View File

@ -4,8 +4,10 @@ import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.Axes
import de.bixilon.minosoft.data.Directions
import de.bixilon.minosoft.gui.rendering.chunk.models.AABB import de.bixilon.minosoft.gui.rendering.chunk.models.AABB
import de.bixilon.minosoft.gui.rendering.util.VecUtil import de.bixilon.minosoft.gui.rendering.util.VecUtil
import de.bixilon.minosoft.gui.rendering.util.VecUtil.getMinDistanceDirection
import glm_.vec3.Vec3 import glm_.vec3.Vec3
import glm_.vec3.Vec3i import glm_.vec3.Vec3i
@ -77,17 +79,22 @@ class VoxelShape(private val aabbs: MutableList<AABB> = mutableListOf()) : Itera
return result return result
} }
fun raycast(position: Vec3, direction: Vec3): Float { data class VoxelShapeRaycastResult(val hit: Boolean, val distance: Float, val direction: Directions)
fun raycast(position: Vec3, direction: Vec3): VoxelShapeRaycastResult {
var minDistance = Float.MAX_VALUE
var minDistanceDirection = Directions.UP
for (aabb in aabbs) { for (aabb in aabbs) {
if (position in aabb) { if (position in aabb) {
return 0f return VoxelShapeRaycastResult(true, 0f, position.getMinDistanceDirection(aabb).inverted)
} }
val current = aabb.raycast(position, direction) val currentDistance = aabb.raycast(position, direction)
if (current >= -0.1f) { if (minDistance > currentDistance) {
return 0f minDistance = currentDistance
minDistanceDirection = (position + direction * currentDistance).getMinDistanceDirection(aabb)
} }
} }
return -1f return VoxelShapeRaycastResult(minDistance != Float.MAX_VALUE, minDistance, minDistanceDirection.inverted)
} }
companion object { companion object {

View File

@ -5,9 +5,7 @@ import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.Axes
import de.bixilon.minosoft.gui.rendering.util.VecUtil import de.bixilon.minosoft.gui.rendering.util.VecUtil
import de.bixilon.minosoft.gui.rendering.util.VecUtil.choose import de.bixilon.minosoft.gui.rendering.util.VecUtil.get
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 de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3
import glm_.Java.Companion.glm import glm_.Java.Companion.glm
import glm_.vec3.Vec3 import glm_.vec3.Vec3
@ -36,9 +34,7 @@ class AABB {
} }
fun intersect(other: AABB): Boolean { fun intersect(other: AABB): Boolean {
return (min.x < other.max.x && max.x > other.min.x) && return (min.x < other.max.x && max.x > other.min.x) && (min.y < other.max.y && max.y > other.min.y) && (min.z < other.max.z && max.z > other.min.z)
(min.y < other.max.y && max.y > other.min.y) &&
(min.z < other.max.z && max.z > other.min.z)
} }
operator fun plus(vec3: Vec3): AABB { operator fun plus(vec3: Vec3): AABB {
@ -50,16 +46,8 @@ class AABB {
} }
operator fun plus(other: AABB): AABB { operator fun plus(other: AABB): AABB {
val newMin = Vec3( val newMin = Vec3(glm.min(min.x, other.min.x), glm.min(min.y, other.min.y), glm.min(min.z, other.min.z))
glm.min(min.x, other.min.x), val newMax = Vec3(glm.max(max.x, other.max.x), glm.max(max.y, other.max.y), glm.max(max.z, other.max.z))
glm.min(min.y, other.min.y),
glm.min(min.z, other.min.z)
)
val newMax = Vec3(
glm.max(max.x, other.max.x),
glm.max(max.y, other.max.y),
glm.max(max.z, other.max.z)
)
return AABB(newMin, newMax) return AABB(newMin, newMax)
} }
@ -151,33 +139,30 @@ class AABB {
} }
fun raycast(position: Vec3, direction: Vec3): Float { fun raycast(position: Vec3, direction: Vec3): Float {
val tMins = getLengthMultipliers(position, direction, min) if (max - min == VecUtil.ONES_VEC3 || position in this) {
val tMaxs = getLengthMultipliers(position, direction, max) return 0f
val tMin = tMins.max
val tMax = tMaxs.min
if (tMax < 0 || tMin > tMax) {
return -1f
} }
return tMin var tMin = 0f
var tMax = +100f
for (axis in Axes.VALUES) {
val t1 = getLengthMultiplier(position, direction, min, axis)
val t2 = getLengthMultiplier(position, direction, max, axis)
tMin = glm.max(tMin, glm.min(t1, t2))
tMax = glm.min(tMax, glm.max(t1, t2))
}
return if (tMax > tMin) {
tMin
} else {
Float.MAX_VALUE
} }
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 { private fun getLengthMultiplier(position: Vec3, direction: Vec3, target: Vec3, axis: Axes): Float {
return (position.choose(axis) - target.choose(axis)) / direction.choose(axis) return (target[axis] - position[axis]) / direction[axis]
} }
operator fun contains(position: Vec3): Boolean { operator fun contains(position: Vec3): Boolean {
return ( return (position.x in min.x..max.x && position.y in min.y..max.y && position.z in min.z..max.z)
position.x in min.x..max.x &&
position.y in min.y..max.y &&
position.z in min.z..max.z )
} }
companion object { companion object {

View File

@ -30,7 +30,6 @@ 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.floor
import de.bixilon.minosoft.gui.rendering.util.VecUtil.getWorldOffset import de.bixilon.minosoft.gui.rendering.util.VecUtil.getWorldOffset
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.nearestIntegerPositionDirection
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
import de.bixilon.minosoft.modding.event.CallbackEventInvoker import de.bixilon.minosoft.modding.event.CallbackEventInvoker
import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.protocol.network.connection.PlayConnection
@ -319,28 +318,26 @@ class Camera(
for (i in 0..RAYCAST_MAX_STEPS) { for (i in 0..RAYCAST_MAX_STEPS) {
val blockPosition = currentPosition.floor val blockPosition = currentPosition.floor
val blockState = connection.world[blockPosition] val blockState = connection.world.getBlockState(blockPosition)
val distance = blockState?.outlineShape?.let { if (blockState != null) {
val aabb = it + blockPosition + blockPosition.getWorldOffset(blockState.block) val voxelShapeRaycastResult = (blockState.outlineShape + blockPosition + blockPosition.getWorldOffset(blockState.block)).raycast(currentPosition, direction)
aabb.raycast(currentPosition, direction) if (voxelShapeRaycastResult.hit) {
} ?: -1.0f currentPosition += direction * voxelShapeRaycastResult.distance
if (distance >= 0.0f && blockState != null) {
currentPosition += direction * distance
return RaycastHit( return RaycastHit(
currentPosition, currentPosition,
getTotalDistance() + distance, blockPosition,
blockState = blockState, getTotalDistance(),
hitDirection = currentPosition.nearestIntegerPositionDirection, blockState,
steps = i, voxelShapeRaycastResult.direction,
i,
) )
} }
currentPosition += direction * (VecUtil.getDistanceToNextIntegerAxis(currentPosition, direction) + 0.001) }
currentPosition += direction * (VecUtil.getDistanceToNextIntegerAxisInDirection(currentPosition, direction) + 0.001)
} }
return null return null
} }
companion object { companion object {
private val CAMERA_UP_VEC3 = Vec3(0.0f, 1.0f, 0.0f) private val CAMERA_UP_VEC3 = Vec3(0.0f, 1.0f, 0.0f)
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.3 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.3

View File

@ -15,16 +15,16 @@ package de.bixilon.minosoft.gui.rendering.input.camera
import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.data.Directions
import de.bixilon.minosoft.data.mappings.blocks.BlockState import de.bixilon.minosoft.data.mappings.blocks.BlockState
import de.bixilon.minosoft.gui.rendering.util.VecUtil.floor
import glm_.vec3.Vec3 import glm_.vec3.Vec3
import glm_.vec3.Vec3i
data class RaycastHit( data class RaycastHit(
val position: Vec3, val position: Vec3,
val blockPosition: Vec3i,
val distance: Float, val distance: Float,
val blockState: BlockState, val blockState: BlockState,
val hitDirection: Directions, val hitDirection: Directions,
val steps: Int, val steps: Int,
) { ) {
val blockPosition = position.floor
val hitPosition = position.minus(blockPosition) val hitPosition = position.minus(blockPosition)
} }

View File

@ -21,8 +21,11 @@ import de.bixilon.minosoft.data.Axes
import de.bixilon.minosoft.data.Directions import de.bixilon.minosoft.data.Directions
import de.bixilon.minosoft.data.mappings.blocks.RandomOffsetTypes import de.bixilon.minosoft.data.mappings.blocks.RandomOffsetTypes
import de.bixilon.minosoft.data.mappings.blocks.types.Block import de.bixilon.minosoft.data.mappings.blocks.types.Block
import de.bixilon.minosoft.gui.rendering.chunk.models.AABB
import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelElement import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelElement
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.MMath.ceilInt
import de.bixilon.minosoft.util.MMath.floorInt
import glm_.func.common.clamp import glm_.func.common.clamp
import glm_.func.common.floor import glm_.func.common.floor
import glm_.func.cos import glm_.func.cos
@ -155,7 +158,11 @@ object VecUtil {
} }
fun Vec3i.Companion.of(chunkPosition: Vec2i, sectionHeight: Int, inChunkSectionPosition: Vec3i): Vec3i { fun Vec3i.Companion.of(chunkPosition: Vec2i, sectionHeight: Int, inChunkSectionPosition: Vec3i): Vec3i {
return Vec3i(chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X + inChunkSectionPosition.x, sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + inChunkSectionPosition.y, chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z + inChunkSectionPosition.z) // ToDo return Vec3i(
chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X + inChunkSectionPosition.x,
sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + inChunkSectionPosition.y,
chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z + inChunkSectionPosition.z
) // ToDo
} }
infix operator fun Vec3i.plus(vec3: Vec3i?): Vec3i { infix operator fun Vec3i.plus(vec3: Vec3i?): Vec3i {
@ -224,27 +231,21 @@ object VecUtil {
return hash shr 16 return hash shr 16
} }
fun getDistanceToNextIntegerAxis(position: Vec3, direction: Vec3): Float { fun getDistanceToNextIntegerAxisInDirection(position: Vec3, direction: Vec3): Float {
val directionXDistance = (direction * getLengthMultiplierToNextIntegerAxisInDirection(direction, position, Axes.X)).length() fun getTarget(direction: Vec3, position: Vec3, axis: Axes): Int {
val directionYDistance = (direction * getLengthMultiplierToNextIntegerAxisInDirection(direction, position, Axes.Y)).length() return if (direction[axis] > 0) {
val directionZDistance = (direction * getLengthMultiplierToNextIntegerAxisInDirection(direction, position, Axes.Z)).length() position[axis].floorInt + 1
return glm.min(directionXDistance, directionYDistance, directionZDistance)
}
private fun getLengthMultiplierToNextIntegerAxisInDirection(direction: Vec3, position: Vec3, axis: Axes): Float {
return (getTargetForNextIntegerAxisInDirection(direction, position, axis) - position.choose(axis)) / direction.choose(axis)
}
private fun getTargetForNextIntegerAxisInDirection(direction: Vec3, position: Vec3, axis: Axes): Int {
return if (direction.choose(axis) > 0) {
position.floor.choose(axis) + 1
} else { } else {
position.floor.choose(axis) position[axis].ceilInt - 1
} }
} }
fun getLengthMultiplier(direction: Vec3, position: Vec3, axis: Axes): Float {
fun Vec3.choose(axis: Axes): Float { return (getTarget(direction, position, axis) - position[axis]) / direction[axis]
return Axes.choose(axis, this) }
val directionXDistance = getLengthMultiplier(direction, position, Axes.X)
val directionYDistance = getLengthMultiplier(direction, position, Axes.Y)
val directionZDistance = getLengthMultiplier(direction, position, Axes.Z)
return glm.min(directionXDistance, directionYDistance, directionZDistance)
} }
val Vec3.min: Float get() = glm.min(this.x, this.y, this.z) val Vec3.min: Float get() = glm.min(this.x, this.y, this.z)
@ -261,16 +262,19 @@ object VecUtil {
return Vec3i(this.x.floor, this.y.floor, this.z.floor) return Vec3i(this.x.floor, this.y.floor, this.z.floor)
} }
fun Vec3i.choose(axis: Axes): Int { fun Vec3.getMinDistanceDirection(aabb: AABB): Directions {
return Axes.choose(axis, this)
}
val Vec3.nearestIntegerPositionDirection: Directions
get() {
var minDistance = Float.MAX_VALUE var minDistance = Float.MAX_VALUE
var minDistanceDirection = Directions.UP var minDistanceDirection = Directions.UP
fun getDistance(position: Vec3, direction: Directions): Float {
val axis = direction.axis
return (position[axis] - this[axis]) * -direction[axis]
}
for (direction in Directions.VALUES) { for (direction in Directions.VALUES) {
val distance = (getTargetForNextIntegerAxisInDirection(direction.directionVector.toVec3, this, direction.axis) - this.choose(direction.axis)) / direction.directionVector.choose(direction.axis) val distance = if (direction[direction.axis] > 0f) {
getDistance(aabb.max, direction)
} else {
getDistance(aabb.min, direction)
}
if (distance < minDistance) { if (distance < minDistance) {
minDistance = distance minDistance = distance
minDistanceDirection = direction minDistanceDirection = direction
@ -279,5 +283,19 @@ object VecUtil {
return minDistanceDirection return minDistanceDirection
} }
val Vec3i.toVec3: Vec3 get() = Vec3(this) operator fun Vec3.get(axis: Axes): Float {
return when(axis) {
Axes.X -> this.x
Axes.Y -> this.y
Axes.Z -> this.z
}
}
operator fun Vec3i.get(axis: Axes): Int {
return when(axis) {
Axes.X -> this.x
Axes.Y -> this.y
Axes.Z -> this.z
}
}
} }

View File

@ -93,13 +93,9 @@ object MMath {
return value - floor(value) return value - floor(value)
} }
fun linearInterpolate(delta: Float, start: Float, end: Float): Float { val Float.floorInt: Int get() = glm.floor(this).toInt()
return start + delta * (end - start)
}
val Float.floor: Float get() = glm.floor(this) val Float.ceilInt: Int get() = glm.ceil(this).toInt()
val Float.fractionalPart: Float get() = this - floor
val Boolean.positiveNegative: Int get() = val Boolean.positiveNegative: Int get() =
if (this) { if (this) {