diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/physics/gravity/CollisionIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/physics/gravity/CollisionIT.kt index 8eb983115..e6f068d48 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/physics/gravity/CollisionIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/physics/gravity/CollisionIT.kt @@ -82,4 +82,32 @@ class CollisionIT { player.assertPosition(0.0, 1.5, 0.0) player.assertVelocity(0.0, -0.0784000015258789, 0.0) } + + fun `not crashing when already exceeding negative y`() { + val player = createPlayer(createSession(2)) + player.forceTeleport(Vec3d(0.0, -3000, 0.0)) + player.physics.velocity = Vec3d(0.0, -150.0, 0.0) + player.runTicks(10) + } + + fun `not crashing when exceeding negative y`() { + val player = createPlayer(createSession(2)) + player.forceTeleport(Vec3d(0.0, -2040, 0.0)) + player.physics.velocity = Vec3d(0.0, -150.0, 0.0) + player.runTicks(10) + } + + fun `not crashing when already exceeding positive y`() { + val player = createPlayer(createSession(2)) + player.forceTeleport(Vec3d(0.0, 3000, 0.0)) + player.physics.velocity = Vec3d(0.0, 150.0, 0.0) + player.runTicks(10) + } + + fun `not crashing when exceeding positive y`() { + val player = createPlayer(createSession(2)) + player.forceTeleport(Vec3d(0.0, 2040, 0.0)) + player.physics.velocity = Vec3d(0.0, 150.0, 0.0) + player.runTicks(10) + } } diff --git a/src/main/java/de/bixilon/minosoft/data/registries/shapes/aabb/AABB.kt b/src/main/java/de/bixilon/minosoft/data/registries/shapes/aabb/AABB.kt index 9b0f88e00..cff3b76de 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/shapes/aabb/AABB.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/shapes/aabb/AABB.kt @@ -17,7 +17,6 @@ import de.bixilon.kotlinglm.func.common.clamp import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kotlinglm.vec3.Vec3d import de.bixilon.kotlinglm.vec3.Vec3i -import de.bixilon.kotlinglm.vec3.Vec3t import de.bixilon.kutil.math.simple.DoubleMath.ceil import de.bixilon.kutil.math.simple.DoubleMath.floor import de.bixilon.minosoft.data.Axes @@ -59,12 +58,13 @@ class AABB { this.max = max } - fun intersect(other: AABB): Boolean { + fun intersects(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) } - operator fun plus(other: Vec3t): AABB = offset(other) - fun offset(other: Vec3t) = AABB(true, min + other, max + other) + fun intersects(other: AABB, offset: BlockPosition): Boolean { + return (min.x < (other.max.x + offset.x) && max.x > (other.min.x + offset.x)) && (min.y < (other.max.y + offset.y) && max.y > (other.min.y + offset.y)) && (min.z < (other.max.z + offset.z) && max.z > (other.min.z + offset.z)) + } operator fun plus(other: Vec3d): AABB = offset(other) fun offset(other: Vec3d) = AABB(true, min + other, max + other) @@ -128,15 +128,10 @@ class AABB { return AABB(true, newMin, newMax) } - fun extend(vec3i: Vec3i): AABB { - return this.extend(Vec3d(vec3i)) - } - fun extend(direction: Directions): AABB { return this.extend(direction.vectord) } - fun grow(size: Double = 1.0E-7): AABB { return AABB(min - size, max + size) } @@ -149,37 +144,38 @@ class AABB { return this > min && this < max } - private fun intersects(axis: Axes, other: AABB): Boolean { + private fun intersects(axis: Axes, other: AABB, offset: BlockPosition): Boolean { val min = min[axis] val max = max[axis] - val otherMin = other.min[axis] - val otherMax = other.max[axis] + val otherMin = other.min[axis] + offset[axis] + val otherMax = other.max[axis] + offset[axis] return min.isIn(otherMin, otherMax) - || max.isIn(otherMin, otherMax) - || otherMin.isIn(min, max) - || otherMax.isIn(min, max) - || (min == otherMin && max == otherMax) + || max.isIn(otherMin, otherMax) + || otherMin.isIn(min, max) + || otherMax.isIn(min, max) + || (min == otherMin && max == otherMax) } - fun calculateMaxOffset(other: AABB, offset: Double, axis: Axes): Double { - if (!intersects(axis.next(), other) || !intersects(axis.previous(), other)) { - return offset + fun calculateMaxDistance(other: AABB, maxDistance: Double, axis: Axes) = calculateMaxDistance(other, BlockPosition(), maxDistance, axis) + fun calculateMaxDistance(other: AABB, offset: BlockPosition, maxDistance: Double, axis: Axes): Double { + if (!intersects(axis.next(), other, offset) || !intersects(axis.previous(), other, offset)) { + return maxDistance } val min = min[axis] val max = max[axis] - val otherMin = other.min[axis] - val otherMax = other.max[axis] + val otherMin = other.min[axis] + offset[axis] + val otherMax = other.max[axis] + offset[axis] - if (offset > 0 && otherMax <= min && otherMax + offset > min) { - return (min - otherMax).clamp(0.0, offset) + if (maxDistance > 0 && otherMax <= min && otherMax + maxDistance > min) { + return (min - otherMax).clamp(0.0, maxDistance) } - if (offset < 0 && max <= otherMin && otherMin + offset < max) { - return (max - otherMin).clamp(offset, 0.0) + if (maxDistance < 0 && max <= otherMin && otherMin + maxDistance < max) { + return (max - otherMin).clamp(maxDistance, 0.0) } - return offset + return maxDistance } @Deprecated("mutable") @@ -188,12 +184,10 @@ class AABB { max.array[axis.ordinal] += value } - fun offset(axis: Axes, offset: Double): AABB { - return when (axis) { - Axes.X -> this + Vec3d(-offset, 0.0, 0.0) - Axes.Y -> this + Vec3d(0.0, -offset, 0.0) - Axes.Z -> this + Vec3d(0.0, 0.0, -offset) - } + fun offset(axis: Axes, offset: Double) = when (axis) { + Axes.X -> this + Vec3d(-offset, 0.0, 0.0) + Axes.Y -> this + Vec3d(0.0, -offset, 0.0) + Axes.Z -> this + Vec3d(0.0, 0.0, -offset) } /** @@ -296,19 +290,19 @@ class AABB { fun checkSide(x: Double): Boolean { return (this.min == Vec3d(x, min.y, min.z) && this.max == Vec3d(x, max.y, min.z)) - || (this.min == Vec3d(x, min.y, min.z) && this.max == Vec3d(x, min.y, max.z)) - || (this.min == Vec3d(x, max.y, min.z) && this.max == Vec3d(x, max.y, max.z)) - || (this.min == Vec3d(x, min.y, max.z) && this.max == Vec3d(x, max.y, max.z)) + || (this.min == Vec3d(x, min.y, min.z) && this.max == Vec3d(x, min.y, max.z)) + || (this.min == Vec3d(x, max.y, min.z) && this.max == Vec3d(x, max.y, max.z)) + || (this.min == Vec3d(x, min.y, max.z) && this.max == Vec3d(x, max.y, max.z)) } return checkSide(min.x) // left quad - || checkSide(max.x) // right quad - // connections between 2 quads - || (this.min == min && this.max == Vec3d(max.x, min.y, min.z)) - || (this.min == Vec3d(min.x, max.y, min.z) && this.max == Vec3d(max.x, max.y, min.z)) - || (this.min == Vec3d(min.x, max.y, max.z) && this.max == max) - || (this.min == Vec3d(min.x, min.y, max.z) && this.max == Vec3d(max.x, min.y, max.z)) + || checkSide(max.x) // right quad + // connections between 2 quads + || (this.min == min && this.max == Vec3d(max.x, min.y, min.z)) + || (this.min == Vec3d(min.x, max.y, min.z) && this.max == Vec3d(max.x, max.y, min.z)) + || (this.min == Vec3d(min.x, max.y, max.z) && this.max == max) + || (this.min == Vec3d(min.x, min.y, max.z) && this.max == Vec3d(max.x, min.y, max.z)) } override fun toString(): String { diff --git a/src/main/java/de/bixilon/minosoft/data/registries/shapes/collision/CollisionShape.kt b/src/main/java/de/bixilon/minosoft/data/registries/shapes/collision/CollisionShape.kt new file mode 100644 index 000000000..d34adca96 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/registries/shapes/collision/CollisionShape.kt @@ -0,0 +1,125 @@ +/* + * Minosoft + * Copyright (C) 2020-2025 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.registries.shapes.collision + +import de.bixilon.kotlinglm.vec3.Vec3d +import de.bixilon.kutil.array.ArrayUtil.cast +import de.bixilon.kutil.cast.CastUtil.unsafeCast +import de.bixilon.minosoft.data.Axes +import de.bixilon.minosoft.data.registries.blocks.shapes.collision.CollisionPredicate +import de.bixilon.minosoft.data.registries.blocks.shapes.collision.context.CollisionContext +import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity +import de.bixilon.minosoft.data.registries.blocks.types.properties.shape.collision.CollidableBlock +import de.bixilon.minosoft.data.registries.blocks.types.properties.shape.collision.fixed.FixedCollidable +import de.bixilon.minosoft.data.registries.shapes.aabb.AABB +import de.bixilon.minosoft.data.registries.shapes.voxel.AbstractVoxelShape +import de.bixilon.minosoft.data.world.World +import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.data.world.iterator.WorldIterator +import de.bixilon.minosoft.data.world.positions.BlockPosition +import de.bixilon.minosoft.gui.rendering.util.allocator.LongAllocator +import de.bixilon.minosoft.gui.rendering.util.allocator.TemporaryAllocator + +class CollisionShape( + val world: World, + context: CollisionContext, + aabb: AABB, + movement: Vec3d, + chunk: Chunk?, + predicate: CollisionPredicate? = null, +) : AbstractVoxelShape { + override val aabbs: Int + + private var total: Int + private val positions: LongArray + private val shapes: Array + + + init { + val aabbs = aabb.extend(movement).grow(1.0).positions() + val positions = POSITIONS.allocate(aabbs.size) + val shapes: Array = SHAPES.allocate(aabbs.size) + + var index = 0 + var total = 0 + + // TODO: add entity collisions (boat, shulker) + // TODO: add world border collision shape + + for ((position, state, chunk) in WorldIterator(aabbs, world, chunk)) { + if (state.block !is CollidableBlock) continue + if (predicate != null && !predicate.invoke(state)) continue + // TODO: filter blocks (e.g. moving piston), whatever that means + + val shape = when (state.block) { + is FixedCollidable -> state.block.getCollisionShape(state) + is BlockWithEntity<*> -> state.block.getCollisionShape(world.session, context, position, state, chunk.getBlockEntity(position.inChunkPosition)) + else -> state.block.getCollisionShape(world.session, context, position, state, null) + } ?: continue + + if (position in aabb && shape.intersects(aabb, -position)) { + continue + } + positions[index] = position.raw + shapes[index] = shape + index++ + total += shape.aabbs + } + this.shapes = shapes.cast() + this.total = index + this.positions = positions + this.aabbs = total + } + + override fun iterator(): Iterator { + TODO("Not yet implemented") + } + + override fun intersects(other: AABB): Boolean { + for (index in 0 until total) { + val position = BlockPosition(this.positions[index]) + val shape = this.shapes[index] + + if (shape.intersects(other, -position)) return true + } + return false + } + + override fun calculateMaxDistance(other: AABB, maxDistance: Double, axis: Axes): Double { + var distance = maxDistance + + for (index in 0 until total) { + val position = BlockPosition(this.positions[index]) + val shape = this.shapes[index] + + distance = shape.calculateMaxDistance(other, -position, distance, axis) + } + + return distance + } + + fun free() { + POSITIONS.free(this.positions) + SHAPES.free(this.shapes.unsafeCast()) + } + + + companion object { + private val POSITIONS = LongAllocator() + private val SHAPES = object : TemporaryAllocator>() { + override fun getSize(value: Array) = value.size + override fun create(size: Int): Array = arrayOfNulls(size) + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/AbstractVoxelShape.kt b/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/AbstractVoxelShape.kt index 90cc4049b..2c90af425 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/AbstractVoxelShape.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/AbstractVoxelShape.kt @@ -16,7 +16,6 @@ package de.bixilon.minosoft.data.registries.shapes.voxel import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kotlinglm.vec3.Vec3d import de.bixilon.kotlinglm.vec3.Vec3i -import de.bixilon.kotlinglm.vec3.Vec3t import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.registries.shapes.ShapeRegistry @@ -30,14 +29,20 @@ import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.max import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.min import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet -abstract class AbstractVoxelShape : Iterable { - abstract val aabbs: Int +interface AbstractVoxelShape : Iterable { + val aabbs: Int - fun intersect(other: AABB): Boolean { + fun intersects(other: AABB): Boolean { for (aabb in this) { - if (!aabb.intersect(other)) { - continue - } + if (!aabb.intersects(other)) continue + return true + } + return false + } + + fun intersects(other: AABB, offset: BlockPosition): Boolean { + for (aabb in this) { + if (!aabb.intersects(other, offset)) continue return true } return false @@ -51,18 +56,14 @@ abstract class AbstractVoxelShape : Iterable { return VoxelShape(result) } - operator fun plus(offset: Vec3t) = modify { it + offset } operator fun plus(offset: Vec3d) = modify { it + offset } operator fun plus(offset: Vec3) = modify { it + offset } operator fun plus(offset: Vec3i) = modify { it + offset } - @JvmName("plusBlockPosition") operator fun plus(offset: BlockPosition) = modify { it + offset } - @JvmName("plusInChunkPosition") operator fun plus(offset: InChunkPosition) = modify { it + offset } - @JvmName("plusInSectionPosition") operator fun plus(offset: InSectionPosition) = modify { it + offset } fun add(other: AbstractVoxelShape): AbstractVoxelShape { @@ -75,7 +76,15 @@ abstract class AbstractVoxelShape : Iterable { fun calculateMaxDistance(other: AABB, maxDistance: Double, axis: Axes): Double { var distance = maxDistance for (aabb in this) { - distance = aabb.calculateMaxOffset(other, distance, axis) + distance = aabb.calculateMaxDistance(other, distance, axis) + } + return distance + } + + fun calculateMaxDistance(other: AABB, offset: BlockPosition, maxDistance: Double, axis: Axes): Double { + var distance = maxDistance + for (aabb in this) { + distance = aabb.calculateMaxDistance(other, offset, distance, axis) } return distance } @@ -110,7 +119,6 @@ abstract class AbstractVoxelShape : Iterable { return shouldDrawLine(start.toVec3d, end.toVec3d) } - fun getMax(axis: Axes): Double { if (aabbs == 0) return Double.NaN diff --git a/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/MutableVoxelShape.kt b/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/MutableVoxelShape.kt deleted file mode 100644 index 055776a8b..000000000 --- a/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/MutableVoxelShape.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. If not, see . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.data.registries.shapes.voxel - -import de.bixilon.minosoft.data.registries.shapes.aabb.AABB - -class MutableVoxelShape( - val aabb: MutableSet, -) : AbstractVoxelShape() { - override val aabbs: Int = aabb.size - - constructor(vararg aabbs: AABB) : this(aabbs.toMutableSet()) - constructor(shape: AbstractVoxelShape) : this(shape.toMutableSet()) - - operator fun plusAssign(aabb: AABB) { - this.aabb += aabb - } - - operator fun plusAssign(shape: AbstractVoxelShape) { - this.aabb += shape - } - - override fun iterator(): Iterator { - return aabb.iterator() - } - - override fun toString(): String { - if (aabbs == 0) { - return "VoxelShape{EMPTY}" - } - return "VoxelShape{$aabb}" - } - - override fun hashCode(): Int { - return aabb.hashCode() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null && aabbs == 0) return true - if (other !is AbstractVoxelShape) return false - if (other is VoxelShape) return aabb == other.aabb - if (other is MutableVoxelShape) return aabb == other.aabb - TODO("Can not compare $this with $other") - } -} diff --git a/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/VoxelShape.kt b/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/VoxelShape.kt index 5766e0c96..6cde423b5 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/VoxelShape.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/VoxelShape.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2025 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -19,7 +19,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet class VoxelShape( val aabb: Collection, -) : AbstractVoxelShape() { +) : AbstractVoxelShape { override val aabbs: Int = aabb.size constructor(vararg aabbs: AABB) : this(ObjectOpenHashSet(aabbs)) @@ -47,7 +47,6 @@ class VoxelShape( if (other == null && aabbs == 0) return true if (other !is AbstractVoxelShape) return false if (other is VoxelShape) return aabb == other.aabb - if (other is MutableVoxelShape) return aabb == other.aabb TODO("Can not compare $this with $other") } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/entities/WorldEntities.kt b/src/main/java/de/bixilon/minosoft/data/world/entities/WorldEntities.kt index 299ebab91..d906408a1 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/entities/WorldEntities.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/entities/WorldEntities.kt @@ -177,7 +177,7 @@ class WorldEntities : Iterable { } val aabb = entity.physics.aabb - if (shape.intersect(aabb)) { + if (shape.intersects(aabb)) { return true } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt b/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt index 8d10d4d11..4ce33f2a7 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt @@ -60,6 +60,7 @@ class WorldIterator( if (this.chunk !== chunk) { this.chunk = chunk } + // TODO: some fast skip? (if section is empty, can not be in section or chunk is null) val state = chunk[position.inChunkPosition] ?: continue @@ -116,7 +117,7 @@ class WorldIterator( if (predicate != null && !predicate.invoke(state)) continue val shape = state.block.getCollisionShape(world.session, context, position, state, null) ?: continue - if ((shape + position).intersect(aabb)) { + if ((shape + position).intersects(aabb)) { return true } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt index 83236f563..44d1711ef 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/positions/BlockPosition.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.data.world.positions +import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.text.formatting.TextFormattable import de.bixilon.minosoft.data.world.positions.BlockPositionUtil.assertPosition @@ -102,6 +103,12 @@ value class BlockPosition( return modifyZ(-Z * 1) } + operator fun get(axis: Axes) = when (axis) { + Axes.X -> x + Axes.Y -> y + Axes.Z -> z + } + inline fun with(x: Int = this.x, y: Int = this.y, z: Int = this.z) = BlockPosition(x, y, z) inline operator fun plus(value: Int) = BlockPosition(this.x + value, this.y + value, this.z + value) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/framebuffer/world/overlay/overlays/simple/WallOverlay.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/framebuffer/world/overlay/overlays/simple/WallOverlay.kt index a2d2718b5..679f96b9c 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/framebuffer/world/overlay/overlays/simple/WallOverlay.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/framebuffer/world/overlay/overlays/simple/WallOverlay.kt @@ -44,7 +44,7 @@ class WallOverlay(context: RenderContext) : SimpleOverlay(context) { } val camera = context.session.camera.entity val shape = blockState.block.getCollisionShape(context.session, EntityCollisionContext(camera), position, blockState, null) ?: return false // TODO: block entity - if (!shape.intersect(player.physics.aabb)) { + if (!shape.intersects(player.physics.aabb)) { return false } return true diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt index 446d15318..aa52ff8ca 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/Particle.kt @@ -20,6 +20,7 @@ import de.bixilon.minosoft.data.physics.PhysicsEntity import de.bixilon.minosoft.data.registries.blocks.shapes.collision.context.ParticleCollisionContext import de.bixilon.minosoft.data.registries.particle.data.ParticleData import de.bixilon.minosoft.data.registries.shapes.aabb.AABB +import de.bixilon.minosoft.data.registries.shapes.collision.CollisionShape import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.gui.rendering.particle.ParticleFactory import de.bixilon.minosoft.gui.rendering.particle.ParticleMesh @@ -28,7 +29,6 @@ import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.blockPosition import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.interpolateLinear -import de.bixilon.minosoft.physics.parts.CollisionMovementPhysics.collectCollisions import de.bixilon.minosoft.physics.parts.CollisionMovementPhysics.collide import de.bixilon.minosoft.protocol.network.session.play.PlaySession import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition @@ -138,7 +138,7 @@ abstract class Particle( private fun collide(movement: Vec3d): Vec3d { val aabb = aabb + movement val context = ParticleCollisionContext(this) - val collisions = session.world.collectCollisions(context, movement, aabb, getChunk()) + val collisions = CollisionShape(session.world, context, aabb, movement, getChunk()) val adjusted = collide(movement, aabb, collisions) if (adjusted.y != movement.y) { onGround = true diff --git a/src/main/java/de/bixilon/minosoft/physics/entities/living/player/local/LocalPlayerPhysics.kt b/src/main/java/de/bixilon/minosoft/physics/entities/living/player/local/LocalPlayerPhysics.kt index 49cd9a904..cb91c77c0 100644 --- a/src/main/java/de/bixilon/minosoft/physics/entities/living/player/local/LocalPlayerPhysics.kt +++ b/src/main/java/de/bixilon/minosoft/physics/entities/living/player/local/LocalPlayerPhysics.kt @@ -108,7 +108,9 @@ class LocalPlayerPhysics(entity: LocalPlayerEntity) : PlayerPhysics = ArrayList() - // TODO: add entity collisions (boat, shulker) - // TODO: add world border collision shape - - - for ((position, state, chunk) in WorldIterator(aabb.extend(movement).grow(1.0).positions(), this, chunk)) { - if (state.block !is CollidableBlock) continue - - if (predicate != null && !predicate.invoke(state)) continue - // TODO: filter blocks (e.g. moving piston), whatever that means - - var shape = when (state.block) { - is FixedCollidable -> state.block.getCollisionShape(state) - is BlockWithEntity<*> -> { - - state.block.getCollisionShape(session, context, position, state, chunk.getBlockEntity(position.inChunkPosition)) - } - - else -> { - state.block.getCollisionShape(session, context, position, state, null) - } - } ?: continue - shape += position - - if (position in aabb && shape.intersect(aabb)) { - continue - } - shapes += shape - } - - return VoxelShape(shapes) - } - - fun EntityPhysics<*>.collectCollisions(movement: Vec3d, aabb: AABB, predicate: CollisionPredicate? = null): VoxelShape { - return this.entity.session.world.collectCollisions(EntityCollisionContext(entity, this, aabb), movement, aabb, positionInfo.chunk, predicate) + fun EntityPhysics<*>.collectCollisions(movement: Vec3d, aabb: AABB, predicate: CollisionPredicate? = null): CollisionShape { + return CollisionShape(this.entity.session.world, EntityCollisionContext(entity, this, aabb), aabb, movement, positionInfo.chunk, predicate) } private fun checkMovement(axis: Axes, originalValue: Double, offsetAABB: Boolean, aabb: AABB, collisions: AbstractVoxelShape): Double { @@ -114,13 +74,19 @@ object CollisionMovementPhysics { fun EntityPhysics<*>.collide(movement: Vec3d): Vec3d { val aabb = aabb - val collisions = collectCollisions(movement, aabb) - val collision = collide(movement, aabb, collisions) - if (stepHeight <= 0.0) { - return collision - } + if (aabb.min.y <= BlockPosition.MIN_Y || aabb.max.y >= BlockPosition.MAX_Y) return movement // TODO: also check movement - return collideStepping(movement, collision, collisions) + val collisions = collectCollisions(movement, aabb) + try { + val collision = collide(movement, aabb, collisions) + if (stepHeight <= 0.0) { + return collision + } + + return collideStepping(movement, collision, collisions) + } finally { + collisions.free() + } } private fun EntityPhysics<*>.collideStepping(movement: Vec3d, collision: Vec3d, collisions: AbstractVoxelShape): Vec3d { diff --git a/src/test/java/de/bixilon/minosoft/data/registries/shapes/AABBTest.kt b/src/test/java/de/bixilon/minosoft/data/registries/shapes/AABBTest.kt index f9e10f9f4..e1d461823 100644 --- a/src/test/java/de/bixilon/minosoft/data/registries/shapes/AABBTest.kt +++ b/src/test/java/de/bixilon/minosoft/data/registries/shapes/AABBTest.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2025 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -33,7 +33,7 @@ internal class AABBTest { val a = AABB(Vec3d(5.0, 0.0, 7.0), Vec3d(6.0, 1.0, 8.0)) val b = AABB(Vec3d(5.7, 1.0, 6.3), Vec3d(6.3, 3, 6.9)) - assertEquals(1239312.0, a.calculateMaxOffset(b, 1239312.0, Axes.Z)) + assertEquals(1239312.0, a.calculateMaxDistance(b, 1239312.0, Axes.Z)) } @Test @@ -41,7 +41,7 @@ internal class AABBTest { val a = AABB(Vec3d(5.0, 0.0, 7.0), Vec3d(6.0, 1.0, 8.0)) val b = AABB(Vec3d(5.699999988079071, 0.5358406250445555, 6.373910529638632), Vec3d(6.300000011920929, 2.3358405773608397, 6.97391055348049)) - assertNotEquals(0.1, a.calculateMaxOffset(b, 0.1, Axes.Z)) + assertNotEquals(0.1, a.calculateMaxDistance(b, 0.1, Axes.Z)) } @Test @@ -49,7 +49,7 @@ internal class AABBTest { val a = AABB(Vec3d(5.0, 0.0, 6.0), Vec3d(5.8, 1.0, 7.0)) val b = AABB(Vec3d(5.7, 1.0, 6.0), Vec3d(6.3, 2.8, 6.6)) - assertEquals(0.0, a.calculateMaxOffset(b, -0.0784000015258789, Axes.Y)) + assertEquals(0.0, a.calculateMaxDistance(b, -0.0784000015258789, Axes.Y)) } @Test @@ -57,7 +57,7 @@ internal class AABBTest { val a = AABB(Vec3d(5.0, 0.0, 6.0), Vec3d(5.8, 1.0, 7.0)) val b = AABB(Vec3d(5.0, 1.0, 6.0), Vec3d(5.8, 2.8, 6.6)) - assertEquals(0.0, a.calculateMaxOffset(b, -0.0784000015258789, Axes.Y)) + assertEquals(0.0, a.calculateMaxDistance(b, -0.0784000015258789, Axes.Y)) } @Test @@ -65,7 +65,7 @@ internal class AABBTest { val a = AABB(Vec3d(5.0, 0.0, 6.0), Vec3d(5.8, 1.0, 7.0)) val b = AABB(Vec3d(5.1, 1.0, 5.9), Vec3d(5.5, 2.8, 7.1)) - assertEquals(0.0, a.calculateMaxOffset(b, -0.0784000015258789, Axes.Y)) + assertEquals(0.0, a.calculateMaxDistance(b, -0.0784000015258789, Axes.Y)) }