1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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>,
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)
}
}
|