Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Spatial partition
  • Loading branch information
AnaghaSasikumar committed Nov 25, 2018
commit 70cd4707caf408bf4d2cef84efacc002487b4612
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
<module>ambassador</module>
<module>acyclic-visitor</module>
<module>collection-pipeline</module>
<module>spatial-partition</module>
</modules>

<repositories>
Expand Down
54 changes: 54 additions & 0 deletions spatial-partition/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
layout: pattern
title: Spatial Partition
folder: spatial-partition
permalink: /patterns/spatial-partition/
categories: Game Programming pattern/Optimisation pattern
tags:
- Java
- Difficulty-Intermediate
---

## Intent
As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom, spatial partition pattern helps to

> efficiently locate objects by storing them in a data structure organized by their positions.

## Applicability
This pattern can be used:
* When you need to keep track of a large number of objects' positions, which are getting updated every frame.
* When it is acceptable to trade memory for speed, since creating and updating data structure will use up extra memory.

## Explanation
Say, you are building a war game with hundreds, or maybe even thousands of players, who are clashing on the battle field. Each player's position is getting updated every frame. The simple way to handle all interactions taking place on the field is to check each player's position against every other player's position:

```java
public void handleMeLee(Unit units[], int numUnits) {
for (int a = 0; a < numUnits - 1; a++)
{
for (int b = a + 1; b < numUnits; b++)
{
if (units[a].position() == units[b].position())
{
handleAttack(units[a], units[b]);
}
}
}
}
```

This will include a lot of unnecessary checks between players which are too far apart to have any influence on each other. The nested loops gives this operation an O(n^2) complexity, which has to be performed every frame since many of the objects on the field may be moving each frame.
The idea behind the Spatial Partition design pattern is to enable quick location of objects using a data structure that is organised by their positions, so when performing an operation like the one above, every object's position need not be checked against all other objects' positions. The data structure can be used to store moving and static objects, though in order to keep track of the moving objects, their positions will have to be reset each time they move. This would mean having to create a new instance of the data structure each time an object moves, which would use up additional memory. The common data structures used for this design pattern are:

* Grid
* Quad tree
* k-d tree
* BSP
* Boundary volume hierarchy

In our implementation, we use the Quadtree data structure which will reduce the time complexity of finding the objects within a certain range from O(n^2) to O(nlogn), decreasing the computations required significantly in case of large number of objects.

## Credits
* [Game Programming Patterns/Spatial Partition](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom
* [Quadtree tutorial](https://www.youtube.com/watch?v=OJxEcs0w_kE) by Daniel Schiffman

9 changes: 9 additions & 0 deletions spatial-partition/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.21.0-SNAPSHOT</version>
</parent>
<artifactId>spatial-partition</artifactId>
</project>
136 changes: 136 additions & 0 deletions spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.iluwatar.spatialpartition;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

/**
* <p>The idea behind the <b>Spatial Partition</b> design pattern is to enable efficient location of objects
* by storing them in a data structure that is organised by their positions. This is especially useful in the
* gaming world, where one may need to look up all the objects within a certain boundary, or near a certain
* other object, repeatedly. The data structure can be used to store moving and static objects, though in order
* to keep track of the moving objects, their positions will have to be reset each time they move. This would
* mean having to create a new instance of the data structure each frame, which would use up additional memory,
* and so this pattern should only be used if one does not mind trading memory for speed and the number of
* objects to keep track of is large to justify the use of the extra space.</p>
* <p>In our example, we use <b>{@link QuadTree} data structure</b> which divides into 4 (quad) sub-sections when
* the number of objects added to it exceeds a certain number (int field capacity). There is also a
* <b>{@link Rect}</b> class to define the boundary of the quadtree. We use an abstract class <b>{@link Point}</b>
* with x and y coordinate fields and also an id field so that it can easily be put and looked up in the hashtable.
* This class has abstract methods to define how the object moves (move()), when to check for collision with any
* object (touches(obj)) and how to handle collision (handleCollision(obj)), and will be extended by any object
* whose position has to be kept track of in the quadtree. The <b>{@link SpatialPartitionGeneric}</b> abstract class
* has 2 fields - a hashtable containing all objects (we use hashtable for faster lookups, insertion and deletion)
* and a quadtree, and contains an abstract method which defines how to handle interactions between objects using
* the quadtree.</p>
* <p>Using the quadtree data structure will reduce the time complexity of finding the objects within a
* certain range from <b>O(n^2) to O(nlogn)</b>, increasing the speed of computations immensely in case of
* large number of objects, which will have a positive effect on the rendering speed of the game.</p>
*/

public class App {

static void noSpatialPartition(int height, int width,
int numOfMovements, Hashtable<Integer, Bubble> bubbles) {
ArrayList<Point> bubblesToCheck = new ArrayList<Point>();
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
bubblesToCheck.add(bubbles.get(e.nextElement())); //all bubbles have to be checked for collision for all bubbles
}

//will run numOfMovement times or till all bubbles have popped
while (numOfMovements > 0 && !bubbles.isEmpty()) {
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
Integer i = e.nextElement();
//bubble moves, new position gets updated, collisions checked with all bubbles in bubblesToCheck
bubbles.get(i).move();
bubbles.replace(i, bubbles.get(i));
bubbles.get(i).handleCollision(bubblesToCheck, bubbles);
}
numOfMovements--;
}
for (Integer key : bubbles.keySet()) {
//bubbles not popped
System.out.println("Bubble " + key + " not popped");
}
}

static void withSpatialPartition(int height, int width,
int numOfMovements, Hashtable<Integer, Bubble> bubbles) {
//creating quadtree
Rect rect = new Rect(width / 2,height / 2,width,height);
QuadTree qTree = new QuadTree(rect, 4);

//will run numOfMovement times or till all bubbles have popped
while (numOfMovements > 0 && !bubbles.isEmpty()) {
//quadtree updated each time
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
qTree.insert(bubbles.get(e.nextElement()));
}
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
Integer i = e.nextElement();
//bubble moves, new position gets updated, quadtree used to reduce computations
bubbles.get(i).move();
bubbles.replace(i, bubbles.get(i));
SpatialPartitionBubbles sp = new SpatialPartitionBubbles(bubbles, qTree);
sp.handleCollisionsUsingQt(bubbles.get(i));
}
numOfMovements--;
}
for (Integer key : bubbles.keySet()) {
//bubbles not popped
System.out.println("Bubble " + key + " not popped");
}
}

/**
* Program entry point.
*
* @param args command line args
*/

public static void main(String[] args) {
Hashtable<Integer, Bubble> bubbles1 = new Hashtable<Integer, Bubble>();
Hashtable<Integer, Bubble> bubbles2 = new Hashtable<Integer, Bubble>();
Random rand = new Random();
for (int i = 0; i < 10000; i++) {
Bubble b = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1);
bubbles1.put(i, b);
bubbles2.put(i, b);
System.out.println("Bubble " + i + " with radius " + b.radius + " added at (" + b.x + "," + b.y + ")");
}

long start1 = System.currentTimeMillis();
App.noSpatialPartition(300,300,20,bubbles1);
long end1 = System.currentTimeMillis();
long start2 = System.currentTimeMillis();
App.withSpatialPartition(300,300,20,bubbles2);
long end2 = System.currentTimeMillis();
System.out.println("Without spatial partition takes " + (end1 - start1) + "ms");
System.out.println("With spatial partition takes " + (end2 - start2) + "ms");
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.iluwatar.spatialpartition;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Random;

/**
* Bubble class extends Point. In this example, we create several bubbles in the field,
* let them move and keep track of which ones have popped and which ones remain.
*/

public class Bubble extends Point<Bubble> {

final int radius;

Bubble(int x, int y, int id, int radius) {
super(x,y,id);
this.radius = radius;
}

void move() {
Random rand = new Random();
//moves by 1 unit in either direction
this.x += rand.nextInt(3) - 1;
this.y += rand.nextInt(3) - 1;
}

boolean touches(Bubble b) {
//distance between them is greater than sum of radii (both sides of equation squared)
return ((this.x - b.x) * (this.x - b.x) + (this.y - b.y) * (this.y - b.y)
<= (this.radius + b.radius) * (this.radius + b.radius));
}

void pop(Hashtable<Integer, Bubble> allBubbles) {
System.out.println("Bubble " + this.id + " popped at (" + this.x + "," + this.y + ")!");
allBubbles.remove(this.id);
}

void handleCollision(ArrayList<Point> bubblesToCheck, Hashtable<Integer, Bubble> allBubbles) {
boolean toBePopped = false; //if any other bubble collides with it, made true
for (int i = 0; i < bubblesToCheck.size(); i++) {
Integer otherId = bubblesToCheck.get(i).id;
if (allBubbles.get(otherId) != null && //the bubble hasn't been popped yet
this.id != otherId && //the two bubbles are not the same
this.touches(allBubbles.get(otherId))) { //the bubbles touch
allBubbles.get(otherId).pop(allBubbles);
toBePopped = true;
}
}
if (toBePopped) {
this.pop(allBubbles);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* The MIT License
* Copyright (c) 2014-2016 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.iluwatar.spatialpartition;

import java.util.ArrayList;
import java.util.Hashtable;

/**
* The abstract Point class which will be extended by any object in the field
* whose location has to be kept track of. Defined by x,y coordinates and an id
* for easy hashing into hashtable.
* @param <T> T will be type subclass
*/

public abstract class Point<T> {

public int x;
public int y;
public final int id;

Point(int x, int y, int id) {
this.x = x;
this.y = y;
this.id = id;
}

/**
* defines how the object moves
*/
abstract void move();

/**
* defines conditions for interacting with an object obj
* @param obj is another object on field which also extends Point
* @return whether the object can interact with the other or not
*/
abstract boolean touches(T obj);

/**
* handling interactions/collisions with other objects
* @param pointsToCheck contains the objects which need to be checked
* @param allPoints contains hashtable of all points on field at this time
*/
abstract void handleCollision(ArrayList<Point> pointsToCheck, Hashtable<Integer, T> allPoints);
}
Loading