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.gui.rendering.chunk.models.FaceSize
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.enum.ValuesEnum
import glm_.vec2.Vec2i
@ -87,12 +88,15 @@ enum class Directions(val directionVector: Vec3i) {
return false
}
operator fun get(axis: Axes): Int {
return directionVector[axis]
}
companion object : BlockPropertiesSerializer, ValuesEnum<Directions> {
override val VALUES = values()
override val NAME_MAP: Map<String, Directions> = KUtil.getEnumValues(VALUES)
val SIDES = arrayOf(NORTH, SOUTH, WEST, EAST)
const val SIDES_OFFSET = 2
override fun deserialize(value: Any): Directions {
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.JsonPrimitive
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.util.VecUtil
import de.bixilon.minosoft.gui.rendering.util.VecUtil.getMinDistanceDirection
import glm_.vec3.Vec3
import glm_.vec3.Vec3i
@ -77,17 +79,22 @@ class VoxelShape(private val aabbs: MutableList<AABB> = mutableListOf()) : Itera
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) {
if (position in aabb) {
return 0f
return VoxelShapeRaycastResult(true, 0f, position.getMinDistanceDirection(aabb).inverted)
}
val current = aabb.raycast(position, direction)
if (current >= -0.1f) {
return 0f
val currentDistance = aabb.raycast(position, direction)
if (minDistance > currentDistance) {
minDistance = currentDistance
minDistanceDirection = (position + direction * currentDistance).getMinDistanceDirection(aabb)
}
}
return -1f
return VoxelShapeRaycastResult(minDistance != Float.MAX_VALUE, minDistance, minDistanceDirection.inverted)
}
companion object {

View File

@ -5,9 +5,7 @@ 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.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.get
import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3
import glm_.Java.Companion.glm
import glm_.vec3.Vec3
@ -36,9 +34,7 @@ class AABB {
}
fun intersect(other: AABB): Boolean {
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)
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)
}
operator fun plus(vec3: Vec3): AABB {
@ -50,16 +46,8 @@ class AABB {
}
operator fun plus(other: AABB): AABB {
val newMin = Vec3(
glm.min(min.x, other.min.x),
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)
)
val newMin = Vec3(glm.min(min.x, other.min.x), 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)
}
@ -151,33 +139,30 @@ 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
if (max - min == VecUtil.ONES_VEC3 || position in this) {
return 0f
}
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 {
return (position.choose(axis) - target.choose(axis)) / direction.choose(axis)
return (target[axis] - position[axis]) / direction[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 )
return (position.x in min.x..max.x && position.y in min.y..max.y && position.z in min.z..max.z)
}
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.getWorldOffset
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.modding.event.CallbackEventInvoker
import de.bixilon.minosoft.protocol.network.connection.PlayConnection
@ -319,28 +318,26 @@ class Camera(
for (i in 0..RAYCAST_MAX_STEPS) {
val blockPosition = currentPosition.floor
val blockState = connection.world[blockPosition]
val distance = blockState?.outlineShape?.let {
val aabb = it + blockPosition + blockPosition.getWorldOffset(blockState.block)
aabb.raycast(currentPosition, direction)
} ?: -1.0f
if (distance >= 0.0f && blockState != null) {
currentPosition += direction * distance
val blockState = connection.world.getBlockState(blockPosition)
if (blockState != null) {
val voxelShapeRaycastResult = (blockState.outlineShape + blockPosition + blockPosition.getWorldOffset(blockState.block)).raycast(currentPosition, direction)
if (voxelShapeRaycastResult.hit) {
currentPosition += direction * voxelShapeRaycastResult.distance
return RaycastHit(
currentPosition,
getTotalDistance() + distance,
blockState = blockState,
hitDirection = currentPosition.nearestIntegerPositionDirection,
steps = i,
blockPosition,
getTotalDistance(),
blockState,
voxelShapeRaycastResult.direction,
i,
)
}
currentPosition += direction * (VecUtil.getDistanceToNextIntegerAxis(currentPosition, direction) + 0.001)
}
currentPosition += direction * (VecUtil.getDistanceToNextIntegerAxisInDirection(currentPosition, direction) + 0.001)
}
return null
}
companion object {
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

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.mappings.blocks.BlockState
import de.bixilon.minosoft.gui.rendering.util.VecUtil.floor
import glm_.vec3.Vec3
import glm_.vec3.Vec3i
data class RaycastHit(
val position: Vec3,
val blockPosition: Vec3i,
val distance: Float,
val blockState: BlockState,
val hitDirection: Directions,
val steps: Int,
) {
val blockPosition = position.floor
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.mappings.blocks.RandomOffsetTypes
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.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.floor
import glm_.func.cos
@ -155,7 +158,11 @@ object VecUtil {
}
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 {
@ -224,27 +231,21 @@ object VecUtil {
return hash shr 16
}
fun getDistanceToNextIntegerAxis(position: Vec3, direction: Vec3): Float {
val directionXDistance = (direction * getLengthMultiplierToNextIntegerAxisInDirection(direction, position, Axes.X)).length()
val directionYDistance = (direction * getLengthMultiplierToNextIntegerAxisInDirection(direction, position, Axes.Y)).length()
val directionZDistance = (direction * getLengthMultiplierToNextIntegerAxisInDirection(direction, position, Axes.Z)).length()
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
fun getDistanceToNextIntegerAxisInDirection(position: Vec3, direction: Vec3): Float {
fun getTarget(direction: Vec3, position: Vec3, axis: Axes): Int {
return if (direction[axis] > 0) {
position[axis].floorInt + 1
} else {
position.floor.choose(axis)
position[axis].ceilInt - 1
}
}
fun Vec3.choose(axis: Axes): Float {
return Axes.choose(axis, this)
fun getLengthMultiplier(direction: Vec3, position: Vec3, axis: Axes): Float {
return (getTarget(direction, position, axis) - position[axis]) / direction[axis]
}
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)
@ -261,16 +262,19 @@ object VecUtil {
return Vec3i(this.x.floor, this.y.floor, this.z.floor)
}
fun Vec3i.choose(axis: Axes): Int {
return Axes.choose(axis, this)
}
val Vec3.nearestIntegerPositionDirection: Directions
get() {
fun Vec3.getMinDistanceDirection(aabb: AABB): Directions {
var minDistance = Float.MAX_VALUE
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) {
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) {
minDistance = distance
minDistanceDirection = direction
@ -279,5 +283,19 @@ object VecUtil {
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)
}
fun linearInterpolate(delta: Float, start: Float, end: Float): Float {
return start + delta * (end - start)
}
val Float.floorInt: Int get() = glm.floor(this).toInt()
val Float.floor: Float get() = glm.floor(this)
val Float.fractionalPart: Float get() = this - floor
val Float.ceilInt: Int get() = glm.ceil(this).toInt()
val Boolean.positiveNegative: Int get() =
if (this) {