From 728ca1ce2f69fdfab38c287f681d725fe52b515e Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 11 Apr 2025 14:01:28 +0200 Subject: [PATCH] physics: reduce memory allocations a lot This should improve performance of physics when exploding millions of tnt. Memory is being reused with the temporary allocator. Shapes are extracted initially, which is still bad, because most of them are empty. --- .../data/physics/gravity/CollisionIT.kt | 28 ++++ .../data/registries/shapes/aabb/AABB.kt | 76 +++++------ .../shapes/collision/CollisionShape.kt | 125 ++++++++++++++++++ .../shapes/voxel/AbstractVoxelShape.kt | 34 +++-- .../shapes/voxel/MutableVoxelShape.kt | 57 -------- .../registries/shapes/voxel/VoxelShape.kt | 5 +- .../data/world/entities/WorldEntities.kt | 2 +- .../data/world/iterator/WorldIterator.kt | 3 +- .../data/world/positions/BlockPosition.kt | 7 + .../overlay/overlays/simple/WallOverlay.kt | 2 +- .../gui/rendering/particle/types/Particle.kt | 4 +- .../living/player/local/LocalPlayerPhysics.kt | 4 +- .../physics/parts/CollisionMovementPhysics.kt | 66 +++------ .../data/registries/shapes/AABBTest.kt | 12 +- 14 files changed, 249 insertions(+), 176 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/registries/shapes/collision/CollisionShape.kt delete mode 100644 src/main/java/de/bixilon/minosoft/data/registries/shapes/voxel/MutableVoxelShape.kt 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)) }