/** An optimised version of Bounce, using a Neighbourhood, which reduces the tests required from O(n²) to O(n). Well, it's not quite O(n) - when the number of balls is so many that there are multiple balls in each Neighbourhood Block, it will climb again. However, in real world scenarios, the number of Actors in each Block will be near to 1. */ class NeighbourhoodBounce : AbstractRole { val velocity = Vector2() var radius = 1.0 var mass = 1.0 var block : Block = null override fun begin() { super.begin() radius = actor.appearance.width() * 0.5 } override fun tick() { val oldBlock = block actor.position += velocity val neighbourhood = (Game.instance.director as NeighbourhoodDirector).neighbourhood val newBlock = neighbourhood.blockAt( actor.x, actor.y ) if (newBlock != oldBlock) { if (oldBlock != null) { oldBlock.remove(this) } newBlock.add(this) block = newBlock } // Bouncing at the edges of the screen is the same as the simple Bounce. val gameInfo = Game.instance.resources.gameInfo val rect = actor.appearance.worldRect() if ( (rect.left < 0 && velocity.x < 0) || (rect.right > gameInfo.width && velocity.x > 0) ) { velocity.x = - velocity.x } if ( (rect.bottom < 0 && velocity.y < 0) || (rect.top > gameInfo.height && velocity.y > 0) ) { velocity.y = - velocity.y } // Now let's do collision detection, but this time, we do NOT need to check EVERY other role, // We only need to check those in this block, and neighbouring blocks for (dx in -1 .. 1 ) { for (dy in -1 .. 1) { // otherBlock is one of the 9 blocks that we need to consider. val otherBlock = block.neighbouringBlock( dx, dy ) if (otherBlock != null) { for ( other in otherBlock.occupants ) { // The rest is the same as the unoptimised Bounce class. // Don't try to collide with myself! if (other != this) { if ( CircularCollision.overlapping( radius + other.radius, actor.position, other.actor.position ) ) { // Perform the collision. val hit = CircularCollision.collision( actor.position, velocity, mass, other.actor.position, other.velocity, other.mass ) if ( hit > 0 ) { actor.event( "hit" ) } } } } } } } } }