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.
This commit is contained in:
Moritz Zwerger 2025-04-11 14:01:28 +02:00
parent 2340fc1a3d
commit 728ca1ce2f
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
14 changed files with 249 additions and 176 deletions

View File

@ -82,4 +82,32 @@ class CollisionIT {
player.assertPosition(0.0, 1.5, 0.0) player.assertPosition(0.0, 1.5, 0.0)
player.assertVelocity(0.0, -0.0784000015258789, 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)
}
} }

View File

@ -17,7 +17,6 @@ import de.bixilon.kotlinglm.func.common.clamp
import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kotlinglm.vec3.Vec3
import de.bixilon.kotlinglm.vec3.Vec3d import de.bixilon.kotlinglm.vec3.Vec3d
import de.bixilon.kotlinglm.vec3.Vec3i 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.ceil
import de.bixilon.kutil.math.simple.DoubleMath.floor import de.bixilon.kutil.math.simple.DoubleMath.floor
import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.Axes
@ -59,12 +58,13 @@ class AABB {
this.max = max 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) 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<out Number>): AABB = offset(other) fun intersects(other: AABB, offset: BlockPosition): Boolean {
fun offset(other: Vec3t<out Number>) = AABB(true, min + other, max + other) 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) operator fun plus(other: Vec3d): AABB = offset(other)
fun offset(other: Vec3d) = AABB(true, min + other, max + other) fun offset(other: Vec3d) = AABB(true, min + other, max + other)
@ -128,15 +128,10 @@ class AABB {
return AABB(true, newMin, newMax) return AABB(true, newMin, newMax)
} }
fun extend(vec3i: Vec3i): AABB {
return this.extend(Vec3d(vec3i))
}
fun extend(direction: Directions): AABB { fun extend(direction: Directions): AABB {
return this.extend(direction.vectord) return this.extend(direction.vectord)
} }
fun grow(size: Double = 1.0E-7): AABB { fun grow(size: Double = 1.0E-7): AABB {
return AABB(min - size, max + size) return AABB(min - size, max + size)
} }
@ -149,37 +144,38 @@ class AABB {
return this > min && this < max 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 min = min[axis]
val max = max[axis] val max = max[axis]
val otherMin = other.min[axis] val otherMin = other.min[axis] + offset[axis]
val otherMax = other.max[axis] val otherMax = other.max[axis] + offset[axis]
return min.isIn(otherMin, otherMax) return min.isIn(otherMin, otherMax)
|| max.isIn(otherMin, otherMax) || max.isIn(otherMin, otherMax)
|| otherMin.isIn(min, max) || otherMin.isIn(min, max)
|| otherMax.isIn(min, max) || otherMax.isIn(min, max)
|| (min == otherMin && max == otherMax) || (min == otherMin && max == otherMax)
} }
fun calculateMaxOffset(other: AABB, offset: Double, axis: Axes): Double { fun calculateMaxDistance(other: AABB, maxDistance: Double, axis: Axes) = calculateMaxDistance(other, BlockPosition(), maxDistance, axis)
if (!intersects(axis.next(), other) || !intersects(axis.previous(), other)) { fun calculateMaxDistance(other: AABB, offset: BlockPosition, maxDistance: Double, axis: Axes): Double {
return offset if (!intersects(axis.next(), other, offset) || !intersects(axis.previous(), other, offset)) {
return maxDistance
} }
val min = min[axis] val min = min[axis]
val max = max[axis] val max = max[axis]
val otherMin = other.min[axis] val otherMin = other.min[axis] + offset[axis]
val otherMax = other.max[axis] val otherMax = other.max[axis] + offset[axis]
if (offset > 0 && otherMax <= min && otherMax + offset > min) { if (maxDistance > 0 && otherMax <= min && otherMax + maxDistance > min) {
return (min - otherMax).clamp(0.0, offset) return (min - otherMax).clamp(0.0, maxDistance)
} }
if (offset < 0 && max <= otherMin && otherMin + offset < max) { if (maxDistance < 0 && max <= otherMin && otherMin + maxDistance < max) {
return (max - otherMin).clamp(offset, 0.0) return (max - otherMin).clamp(maxDistance, 0.0)
} }
return offset return maxDistance
} }
@Deprecated("mutable") @Deprecated("mutable")
@ -188,12 +184,10 @@ class AABB {
max.array[axis.ordinal] += value max.array[axis.ordinal] += value
} }
fun offset(axis: Axes, offset: Double): AABB { fun offset(axis: Axes, offset: Double) = when (axis) {
return when (axis) { Axes.X -> this + Vec3d(-offset, 0.0, 0.0)
Axes.X -> this + Vec3d(-offset, 0.0, 0.0) Axes.Y -> this + Vec3d(0.0, -offset, 0.0)
Axes.Y -> this + Vec3d(0.0, -offset, 0.0) Axes.Z -> this + Vec3d(0.0, 0.0, -offset)
Axes.Z -> this + Vec3d(0.0, 0.0, -offset)
}
} }
/** /**
@ -296,19 +290,19 @@ class AABB {
fun checkSide(x: Double): Boolean { fun checkSide(x: Double): Boolean {
return (this.min == Vec3d(x, min.y, min.z) && this.max == Vec3d(x, max.y, min.z)) 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, 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, 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, max.z) && this.max == Vec3d(x, max.y, max.z))
} }
return checkSide(min.x) // left quad return checkSide(min.x) // left quad
|| checkSide(max.x) // right quad || checkSide(max.x) // right quad
// connections between 2 quads // connections between 2 quads
|| (this.min == min && this.max == Vec3d(max.x, min.y, min.z)) || (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, 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, 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)) || (this.min == Vec3d(min.x, min.y, max.z) && this.max == Vec3d(max.x, min.y, max.z))
} }
override fun toString(): String { override fun toString(): String {

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<AbstractVoxelShape>
init {
val aabbs = aabb.extend(movement).grow(1.0).positions()
val positions = POSITIONS.allocate(aabbs.size)
val shapes: Array<AbstractVoxelShape?> = 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<AABB> {
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<Array<AbstractVoxelShape?>>() {
override fun getSize(value: Array<AbstractVoxelShape?>) = value.size
override fun create(size: Int): Array<AbstractVoxelShape?> = arrayOfNulls(size)
}
}
}

View File

@ -16,7 +16,6 @@ package de.bixilon.minosoft.data.registries.shapes.voxel
import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kotlinglm.vec3.Vec3
import de.bixilon.kotlinglm.vec3.Vec3d import de.bixilon.kotlinglm.vec3.Vec3d
import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kotlinglm.vec3.Vec3t
import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.kutil.primitive.IntUtil.toInt
import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.Axes
import de.bixilon.minosoft.data.registries.shapes.ShapeRegistry 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 de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.min
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
abstract class AbstractVoxelShape : Iterable<AABB> { interface AbstractVoxelShape : Iterable<AABB> {
abstract val aabbs: Int val aabbs: Int
fun intersect(other: AABB): Boolean { fun intersects(other: AABB): Boolean {
for (aabb in this) { for (aabb in this) {
if (!aabb.intersect(other)) { if (!aabb.intersects(other)) continue
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 true
} }
return false return false
@ -51,18 +56,14 @@ abstract class AbstractVoxelShape : Iterable<AABB> {
return VoxelShape(result) return VoxelShape(result)
} }
operator fun plus(offset: Vec3t<out Number>) = modify { it + offset }
operator fun plus(offset: Vec3d) = modify { it + offset } operator fun plus(offset: Vec3d) = modify { it + offset }
operator fun plus(offset: Vec3) = modify { it + offset } operator fun plus(offset: Vec3) = modify { it + offset }
operator fun plus(offset: Vec3i) = modify { it + offset } operator fun plus(offset: Vec3i) = modify { it + offset }
@JvmName("plusBlockPosition")
operator fun plus(offset: BlockPosition) = modify { it + offset } operator fun plus(offset: BlockPosition) = modify { it + offset }
@JvmName("plusInChunkPosition")
operator fun plus(offset: InChunkPosition) = modify { it + offset } operator fun plus(offset: InChunkPosition) = modify { it + offset }
@JvmName("plusInSectionPosition")
operator fun plus(offset: InSectionPosition) = modify { it + offset } operator fun plus(offset: InSectionPosition) = modify { it + offset }
fun add(other: AbstractVoxelShape): AbstractVoxelShape { fun add(other: AbstractVoxelShape): AbstractVoxelShape {
@ -75,7 +76,15 @@ abstract class AbstractVoxelShape : Iterable<AABB> {
fun calculateMaxDistance(other: AABB, maxDistance: Double, axis: Axes): Double { fun calculateMaxDistance(other: AABB, maxDistance: Double, axis: Axes): Double {
var distance = maxDistance var distance = maxDistance
for (aabb in this) { 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 return distance
} }
@ -110,7 +119,6 @@ abstract class AbstractVoxelShape : Iterable<AABB> {
return shouldDrawLine(start.toVec3d, end.toVec3d) return shouldDrawLine(start.toVec3d, end.toVec3d)
} }
fun getMax(axis: Axes): Double { fun getMax(axis: Axes): Double {
if (aabbs == 0) return Double.NaN if (aabbs == 0) return Double.NaN

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<AABB>,
) : 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<AABB> {
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")
}
}

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * 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. * 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( class VoxelShape(
val aabb: Collection<AABB>, val aabb: Collection<AABB>,
) : AbstractVoxelShape() { ) : AbstractVoxelShape {
override val aabbs: Int = aabb.size override val aabbs: Int = aabb.size
constructor(vararg aabbs: AABB) : this(ObjectOpenHashSet(aabbs)) constructor(vararg aabbs: AABB) : this(ObjectOpenHashSet(aabbs))
@ -47,7 +47,6 @@ class VoxelShape(
if (other == null && aabbs == 0) return true if (other == null && aabbs == 0) return true
if (other !is AbstractVoxelShape) return false if (other !is AbstractVoxelShape) return false
if (other is VoxelShape) return aabb == other.aabb if (other is VoxelShape) return aabb == other.aabb
if (other is MutableVoxelShape) return aabb == other.aabb
TODO("Can not compare $this with $other") TODO("Can not compare $this with $other")
} }
} }

View File

@ -177,7 +177,7 @@ class WorldEntities : Iterable<Entity> {
} }
val aabb = entity.physics.aabb val aabb = entity.physics.aabb
if (shape.intersect(aabb)) { if (shape.intersects(aabb)) {
return true return true
} }
} }

View File

@ -60,6 +60,7 @@ class WorldIterator(
if (this.chunk !== chunk) { if (this.chunk !== chunk) {
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 val state = chunk[position.inChunkPosition] ?: continue
@ -116,7 +117,7 @@ class WorldIterator(
if (predicate != null && !predicate.invoke(state)) continue if (predicate != null && !predicate.invoke(state)) continue
val shape = state.block.getCollisionShape(world.session, context, position, state, null) ?: 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 return true
} }
} }

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.data.world.positions 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.direction.Directions
import de.bixilon.minosoft.data.text.formatting.TextFormattable import de.bixilon.minosoft.data.text.formatting.TextFormattable
import de.bixilon.minosoft.data.world.positions.BlockPositionUtil.assertPosition import de.bixilon.minosoft.data.world.positions.BlockPositionUtil.assertPosition
@ -102,6 +103,12 @@ value class BlockPosition(
return modifyZ(-Z * 1) 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 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) inline operator fun plus(value: Int) = BlockPosition(this.x + value, this.y + value, this.z + value)

View File

@ -44,7 +44,7 @@ class WallOverlay(context: RenderContext) : SimpleOverlay(context) {
} }
val camera = context.session.camera.entity val camera = context.session.camera.entity
val shape = blockState.block.getCollisionShape(context.session, EntityCollisionContext(camera), position, blockState, null) ?: return false // TODO: block 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 false
} }
return true return true

View File

@ -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.blocks.shapes.collision.context.ParticleCollisionContext
import de.bixilon.minosoft.data.registries.particle.data.ParticleData 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.aabb.AABB
import de.bixilon.minosoft.data.registries.shapes.collision.CollisionShape
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.gui.rendering.particle.ParticleFactory import de.bixilon.minosoft.gui.rendering.particle.ParticleFactory
import de.bixilon.minosoft.gui.rendering.particle.ParticleMesh 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.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.blockPosition 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.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.physics.parts.CollisionMovementPhysics.collide
import de.bixilon.minosoft.protocol.network.session.play.PlaySession import de.bixilon.minosoft.protocol.network.session.play.PlaySession
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
@ -138,7 +138,7 @@ abstract class Particle(
private fun collide(movement: Vec3d): Vec3d { private fun collide(movement: Vec3d): Vec3d {
val aabb = aabb + movement val aabb = aabb + movement
val context = ParticleCollisionContext(this) 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) val adjusted = collide(movement, aabb, collisions)
if (adjusted.y != movement.y) { if (adjusted.y != movement.y) {
onGround = true onGround = true

View File

@ -108,7 +108,9 @@ class LocalPlayerPhysics(entity: LocalPlayerEntity) : PlayerPhysics<LocalPlayerE
val aabb = aabb val aabb = aabb
val offset = AABB(position.x + 0.0, aabb.min.y, position.z + 0.0, position.x + 1.0, aabb.max.y, position.z + 1.0).shrink(1.0E-7) val offset = AABB(position.x + 0.0, aabb.min.y, position.z + 0.0, position.x + 1.0, aabb.max.y, position.z + 1.0).shrink(1.0E-7)
val collisions = collectCollisions(Vec3d.EMPTY, offset, predicate = predicate) val collisions = collectCollisions(Vec3d.EMPTY, offset, predicate = predicate)
return collisions.intersect(aabb) val intersects = collisions.intersects(aabb)
collisions.free()
return intersects
} }
private fun shouldSprint(): Boolean { private fun shouldSprint(): Boolean {

View File

@ -17,59 +17,19 @@ import de.bixilon.kotlinglm.vec3.Vec3d
import de.bixilon.kotlinglm.vec3.swizzle.xz import de.bixilon.kotlinglm.vec3.swizzle.xz
import de.bixilon.minosoft.data.Axes 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.CollisionPredicate
import de.bixilon.minosoft.data.registries.blocks.shapes.collision.context.CollisionContext
import de.bixilon.minosoft.data.registries.blocks.shapes.collision.context.EntityCollisionContext import de.bixilon.minosoft.data.registries.blocks.shapes.collision.context.EntityCollisionContext
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.aabb.AABB
import de.bixilon.minosoft.data.registries.shapes.collision.CollisionShape
import de.bixilon.minosoft.data.registries.shapes.voxel.AbstractVoxelShape import de.bixilon.minosoft.data.registries.shapes.voxel.AbstractVoxelShape
import de.bixilon.minosoft.data.registries.shapes.voxel.VoxelShape import de.bixilon.minosoft.data.world.positions.BlockPosition
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.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY
import de.bixilon.minosoft.physics.entities.EntityPhysics import de.bixilon.minosoft.physics.entities.EntityPhysics
import kotlin.math.abs import kotlin.math.abs
object CollisionMovementPhysics { object CollisionMovementPhysics {
fun World.collectCollisions(context: CollisionContext, movement: Vec3d, aabb: AABB, chunk: Chunk?, predicate: CollisionPredicate? = null): VoxelShape { fun EntityPhysics<*>.collectCollisions(movement: Vec3d, aabb: AABB, predicate: CollisionPredicate? = null): CollisionShape {
val shapes: ArrayList<AABB> = ArrayList() return CollisionShape(this.entity.session.world, EntityCollisionContext(entity, this, aabb), aabb, movement, positionInfo.chunk, predicate)
// 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)
} }
private fun checkMovement(axis: Axes, originalValue: Double, offsetAABB: Boolean, aabb: AABB, collisions: AbstractVoxelShape): Double { 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 { fun EntityPhysics<*>.collide(movement: Vec3d): Vec3d {
val aabb = aabb val aabb = aabb
val collisions = collectCollisions(movement, aabb) if (aabb.min.y <= BlockPosition.MIN_Y || aabb.max.y >= BlockPosition.MAX_Y) return movement // TODO: also check movement
val collision = collide(movement, aabb, collisions)
if (stepHeight <= 0.0) {
return collision
}
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 { private fun EntityPhysics<*>.collideStepping(movement: Vec3d, collision: Vec3d, collisions: AbstractVoxelShape): Vec3d {

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * 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. * 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 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)) 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 @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 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)) 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 @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 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)) 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 @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 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)) 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 @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 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)) 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))
} }