Я бы не стал использовать QuadTrees или что-нибудь более сложное, потому что вы будете постоянно балансировать и перебалансировать деревья, когда шары перемещаются между ними. Вероятно, было бы эффективнее иметь сетку - каждый раз, когда вы перемещаете шар, вы можете просто вычислить, в какой ячейке сетки он находится, и добавить его туда, если он изменился. И каждый раз, когда вам нужно выполнить проверку столкновения, вы можете просто проверять шары, центр которых находится либо в вашей сетке, либо в соседних, если вы достаточно близко к краю.
Вы можете поэкспериментировать с размером сетки, чтобы найти оптимальный. Вы можете обнаружить, что это зависит от того, сколько у вас шаров.
Я сказал это в комментарии ниже, но я думаю, что это заслуживает того, чтобы быть частью ответа:
Обнаружение столкновений выполняется только тогда, когда что-то движется, поэтому вам нужно только проверять движущуюся вещь по предметам в ее квадрате сетки (и соседним, как упомянуто выше). Таким образом, если вы получите кучу вещей в нижней части, которые не движутся, довольно скоро эти объекты никогда не проверяются на столкновение, потому что ничто не движется в пределах их сетки и ничего не движется в или из их сетки.