summaryrefslogtreecommitdiff
path: root/composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-10-26 21:38:22 -0700
committerElizabeth Hunt <me@liz.coffee>2025-10-26 21:39:58 -0700
commita8e5e723b7e1891c9b352261a3ee4c3d3563e8cf (patch)
tree853df79c877d37d7e5d25f52b301aedcc3d5db55 /composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt
parent395aa7d1c312e495517701be11c21425d9a5838e (diff)
downloadabstraction-engine-kt-main.tar.gz
abstraction-engine-kt-main.zip
Checkpoint twoHEADmain
Diffstat (limited to 'composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt')
-rw-r--r--composeApp/src/commonMain/kotlin/coffee/liz/ecs/physics/CollisionTick.kt95
1 files changed, 78 insertions, 17 deletions
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<Outside>(
- private val collisionResolver: CollisionResolver<Outside>
-) {
- fun runTick(world: World<Outside>) {
- // 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<Outside>,
+ private val subgridDimension: Vec2 = Vec2(50f, 50f)
+) : PhysicsTick<Outside> {
+ override fun runTick(world: World<Outside>) {
+ 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<Outside>, potentialCollisions: Collection<Entity>) {
+ 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<Outside>, 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<Rect> {
+ 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<Rect>.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