From a8e5e723b7e1891c9b352261a3ee4c3d3563e8cf Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 26 Oct 2025 21:38:22 -0700 Subject: Checkpoint two --- .../kotlin/coffee/liz/ecs/physics/CollisionTick.kt | 95 ++++++++++++++++++---- 1 file changed, 78 insertions(+), 17 deletions(-) (limited to 'composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt') diff --git a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt index bc34ac1..d7d4dda 100644 --- a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt +++ b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt @@ -1,28 +1,89 @@ package coffee.liz.ecs.physics +import coffee.liz.ecs.Entity import coffee.liz.ecs.Rect +import coffee.liz.ecs.Vec2 import coffee.liz.ecs.World +import kotlin.math.min internal class CollisionTick( - private val collisionResolver: CollisionResolver -) { - fun runTick(world: World) { - // Eh, fast enough for now. Don't need to do any fancy collision detection. There's always later to improve if - // it's that bad. - world.query(Collidable::class, Position::class).forEach { a -> - world.query(Collidable::class, Position::class).forEach { b -> - val aHitBoxes = a.get(Position::class).let { pos -> a.get(Collidable::class).hitboxes.map { - Rect(pos.vec2, it.dimensions) - }} - val bHitBoxes = b.get(Position::class).let { pos -> b.get(Collidable::class).hitboxes.map { - Rect(pos.vec2, it.dimensions) - }} - - val collisionDetected = aHitBoxes.any { a -> bHitBoxes.any { b -> a.overlaps(b) } } - if (collisionDetected) { - collisionResolver.resolveCollision(world, a, b) + private val collisionResolver: CollisionResolver, + private val subgridDimension: Vec2 = Vec2(50f, 50f) +) : PhysicsTick { + override fun runTick(world: World) { + val subgridEntities = world.collidingBounds().split(subgridDimension).map { subgrid -> + subgrid.map { cell -> + world.query(Colliding::class, Position::class).filter { entity -> + entity.positionalHitBoxes().any { cell.overlaps(it) } } } } + + for (y in subgridEntities.indices) { + for (x in subgridEntities[y].indices) { + resolveCollisions(world, subgridEntities[y][x]) + } + } + } + + private fun resolveCollisions(world: World, potentialCollisions: Collection) { + potentialCollisions.forEach { from -> + potentialCollisions.filter { to -> + val fromColliding = from.get(Colliding::class) + val toColliding = to.get(Colliding::class) + + val canCollide = fromColliding.group.collideInto.contains(toColliding.group.collisionGroup) + if (!canCollide) { + return@filter false + } + + val fromHitBoxes = from.positionalHitBoxes() + val toHitBoxes = to.positionalHitBoxes() + + fromHitBoxes.any { a -> toHitBoxes.any { b -> a.overlaps(b) } } + }.forEach { collision -> propagateCollision(world, from, collision)} + } + } + + private fun propagateCollision(world: World, from: Entity, to: Entity) { + collisionResolver.resolveCollision(world, from, to) + + val fromVelocity = from.takeIf { it.has(Velocity::class) }?.get(Velocity::class) ?: return + + val toVelocity = to.takeIf { it.has(Velocity::class) }?.get(Velocity::class) + ?: Velocity(Vec2(0f, 0f)) + val toColliding = to.get(Colliding::class) + + if (toColliding.propagateMovement) { + to.add(Velocity(toVelocity.velocity.plus(fromVelocity.velocity))) + } + } +} + +private fun Entity.positionalHitBoxes(): Collection { + val pos = get(Position::class) + val hitboxes = get(Colliding::class).hitboxes + return hitboxes.map { Rect(pos.position.plus(it.topLeft), it.dimensions) } +} + +fun World<*>.collidingBounds(): Rect { + return query(Position::class, Colliding::class).map { entity -> + val topLeft = entity.get(Position::class).position + val dimensions = entity.get(Colliding::class).hitboxes.merge().dimensions + Rect(topLeft, dimensions) + }.merge() +} + +fun Collection.merge(): Rect { + return reduce { a, b -> + val newTopLeft = Vec2( + min(a.topLeft.x, b.topLeft.x), + min(a.topLeft.y, b.topLeft.y) + ) + val dimensions = Vec2( + maxOf(a.topLeft.x + a.dimensions.x, b.topLeft.x + b.dimensions.x) - newTopLeft.x, + maxOf(a.topLeft.y + a.dimensions.y, b.topLeft.y + b.dimensions.y) - newTopLeft.y + ) + Rect(newTopLeft, dimensions) } } \ No newline at end of file -- cgit v1.2.3-70-g09d2