Skip to content

Commit a6749cb

Browse files
AnaghaSasikumariluwatar
authored andcommitted
Spatial partition pattern iluwatar#562 (iluwatar#828)
* Spatial partition * Update pom.xml * Update Bubble.java - pmd * Update Rect.java - pmd
1 parent 7b8c9b0 commit a6749cb

File tree

14 files changed

+914
-0
lines changed

14 files changed

+914
-0
lines changed

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
<module>ambassador</module>
165165
<module>acyclic-visitor</module>
166166
<module>collection-pipeline</module>
167+
<module>spatial-partition</module>
167168
</modules>
168169

169170
<repositories>

spatial-partition/README.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
layout: pattern
3+
title: Spatial Partition
4+
folder: spatial-partition
5+
permalink: /patterns/spatial-partition/
6+
categories: Game Programming pattern/Optimisation pattern
7+
tags:
8+
- Java
9+
- Difficulty-Intermediate
10+
---
11+
12+
## Intent
13+
As explained in the book [Game Programming Patterns](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom, spatial partition pattern helps to
14+
15+
> efficiently locate objects by storing them in a data structure organized by their positions.
16+
17+
## Applicability
18+
This pattern can be used:
19+
* When you need to keep track of a large number of objects' positions, which are getting updated every frame.
20+
* When it is acceptable to trade memory for speed, since creating and updating data structure will use up extra memory.
21+
22+
## Explanation
23+
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:
24+
25+
```java
26+
public void handleMeLee(Unit units[], int numUnits) {
27+
for (int a = 0; a < numUnits - 1; a++)
28+
{
29+
for (int b = a + 1; b < numUnits; b++)
30+
{
31+
if (units[a].position() == units[b].position())
32+
{
33+
handleAttack(units[a], units[b]);
34+
}
35+
}
36+
}
37+
}
38+
```
39+
40+
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.
41+
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:
42+
43+
* Grid
44+
* Quad tree
45+
* k-d tree
46+
* BSP
47+
* Boundary volume hierarchy
48+
49+
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.
50+
51+
## Credits
52+
* [Game Programming Patterns/Spatial Partition](http://gameprogrammingpatterns.com/spatial-partition.html) by Bob Nystrom
53+
* [Quadtree tutorial](https://www.youtube.com/watch?v=OJxEcs0w_kE) by Daniel Schiffman
54+

spatial-partition/pom.xml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<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">
2+
<!--
3+
The MIT License
4+
Copyright (c) 2014-2016 Ilkka Seppälä
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
The above copyright notice and this permission notice shall be included in
12+
all copies or substantial portions of the Software.
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
20+
-->
21+
<modelVersion>4.0.0</modelVersion>
22+
<parent>
23+
<groupId>com.iluwatar</groupId>
24+
<artifactId>java-design-patterns</artifactId>
25+
<version>1.21.0-SNAPSHOT</version>
26+
</parent>
27+
<artifactId>spatial-partition</artifactId>
28+
<dependencies>
29+
<dependency>
30+
<groupId>org.junit.jupiter</groupId>
31+
<artifactId>junit-jupiter-api</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.junit.jupiter</groupId>
36+
<artifactId>junit-jupiter-engine</artifactId>
37+
<scope>test</scope>
38+
</dependency>
39+
</dependencies>
40+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014-2016 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.spatialpartition;
25+
26+
import java.util.ArrayList;
27+
import java.util.Enumeration;
28+
import java.util.Hashtable;
29+
import java.util.Random;
30+
31+
/**
32+
* <p>The idea behind the <b>Spatial Partition</b> design pattern is to enable efficient location of objects
33+
* by storing them in a data structure that is organised by their positions. This is especially useful in the
34+
* gaming world, where one may need to look up all the objects within a certain boundary, or near a certain
35+
* other object, repeatedly. The data structure can be used to store moving and static objects, though in order
36+
* to keep track of the moving objects, their positions will have to be reset each time they move. This would
37+
* mean having to create a new instance of the data structure each frame, which would use up additional memory,
38+
* and so this pattern should only be used if one does not mind trading memory for speed and the number of
39+
* objects to keep track of is large to justify the use of the extra space.</p>
40+
* <p>In our example, we use <b>{@link QuadTree} data structure</b> which divides into 4 (quad) sub-sections when
41+
* the number of objects added to it exceeds a certain number (int field capacity). There is also a
42+
* <b>{@link Rect}</b> class to define the boundary of the quadtree. We use an abstract class <b>{@link Point}</b>
43+
* with x and y coordinate fields and also an id field so that it can easily be put and looked up in the hashtable.
44+
* This class has abstract methods to define how the object moves (move()), when to check for collision with any
45+
* object (touches(obj)) and how to handle collision (handleCollision(obj)), and will be extended by any object
46+
* whose position has to be kept track of in the quadtree. The <b>{@link SpatialPartitionGeneric}</b> abstract class
47+
* has 2 fields - a hashtable containing all objects (we use hashtable for faster lookups, insertion and deletion)
48+
* and a quadtree, and contains an abstract method which defines how to handle interactions between objects using
49+
* the quadtree.</p>
50+
* <p>Using the quadtree data structure will reduce the time complexity of finding the objects within a
51+
* certain range from <b>O(n^2) to O(nlogn)</b>, increasing the speed of computations immensely in case of
52+
* large number of objects, which will have a positive effect on the rendering speed of the game.</p>
53+
*/
54+
55+
public class App {
56+
57+
static void noSpatialPartition(int height, int width,
58+
int numOfMovements, Hashtable<Integer, Bubble> bubbles) {
59+
ArrayList<Point> bubblesToCheck = new ArrayList<Point>();
60+
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
61+
bubblesToCheck.add(bubbles.get(e.nextElement())); //all bubbles have to be checked for collision for all bubbles
62+
}
63+
64+
//will run numOfMovement times or till all bubbles have popped
65+
while (numOfMovements > 0 && !bubbles.isEmpty()) {
66+
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
67+
Integer i = e.nextElement();
68+
//bubble moves, new position gets updated, collisions checked with all bubbles in bubblesToCheck
69+
bubbles.get(i).move();
70+
bubbles.replace(i, bubbles.get(i));
71+
bubbles.get(i).handleCollision(bubblesToCheck, bubbles);
72+
}
73+
numOfMovements--;
74+
}
75+
for (Integer key : bubbles.keySet()) {
76+
//bubbles not popped
77+
System.out.println("Bubble " + key + " not popped");
78+
}
79+
}
80+
81+
static void withSpatialPartition(int height, int width,
82+
int numOfMovements, Hashtable<Integer, Bubble> bubbles) {
83+
//creating quadtree
84+
Rect rect = new Rect(width / 2,height / 2,width,height);
85+
QuadTree qTree = new QuadTree(rect, 4);
86+
87+
//will run numOfMovement times or till all bubbles have popped
88+
while (numOfMovements > 0 && !bubbles.isEmpty()) {
89+
//quadtree updated each time
90+
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
91+
qTree.insert(bubbles.get(e.nextElement()));
92+
}
93+
for (Enumeration<Integer> e = bubbles.keys(); e.hasMoreElements();) {
94+
Integer i = e.nextElement();
95+
//bubble moves, new position gets updated, quadtree used to reduce computations
96+
bubbles.get(i).move();
97+
bubbles.replace(i, bubbles.get(i));
98+
SpatialPartitionBubbles sp = new SpatialPartitionBubbles(bubbles, qTree);
99+
sp.handleCollisionsUsingQt(bubbles.get(i));
100+
}
101+
numOfMovements--;
102+
}
103+
for (Integer key : bubbles.keySet()) {
104+
//bubbles not popped
105+
System.out.println("Bubble " + key + " not popped");
106+
}
107+
}
108+
109+
/**
110+
* Program entry point.
111+
*
112+
* @param args command line args
113+
*/
114+
115+
public static void main(String[] args) {
116+
Hashtable<Integer, Bubble> bubbles1 = new Hashtable<Integer, Bubble>();
117+
Hashtable<Integer, Bubble> bubbles2 = new Hashtable<Integer, Bubble>();
118+
Random rand = new Random();
119+
for (int i = 0; i < 10000; i++) {
120+
Bubble b = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1);
121+
bubbles1.put(i, b);
122+
bubbles2.put(i, b);
123+
System.out.println("Bubble " + i + " with radius " + b.radius + " added at (" + b.x + "," + b.y + ")");
124+
}
125+
126+
long start1 = System.currentTimeMillis();
127+
App.noSpatialPartition(300,300,20,bubbles1);
128+
long end1 = System.currentTimeMillis();
129+
long start2 = System.currentTimeMillis();
130+
App.withSpatialPartition(300,300,20,bubbles2);
131+
long end2 = System.currentTimeMillis();
132+
System.out.println("Without spatial partition takes " + (end1 - start1) + "ms");
133+
System.out.println("With spatial partition takes " + (end2 - start2) + "ms");
134+
}
135+
}
136+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014-2016 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.spatialpartition;
25+
26+
import java.util.ArrayList;
27+
import java.util.Hashtable;
28+
import java.util.Random;
29+
30+
/**
31+
* Bubble class extends Point. In this example, we create several bubbles in the field,
32+
* let them move and keep track of which ones have popped and which ones remain.
33+
*/
34+
35+
public class Bubble extends Point<Bubble> {
36+
37+
final int radius;
38+
39+
Bubble(int x, int y, int id, int radius) {
40+
super(x,y,id);
41+
this.radius = radius;
42+
}
43+
44+
void move() {
45+
Random rand = new Random();
46+
//moves by 1 unit in either direction
47+
this.x += rand.nextInt(3) - 1;
48+
this.y += rand.nextInt(3) - 1;
49+
}
50+
51+
boolean touches(Bubble b) {
52+
//distance between them is greater than sum of radii (both sides of equation squared)
53+
return (this.x - b.x) * (this.x - b.x) + (this.y - b.y) * (this.y - b.y)
54+
<= (this.radius + b.radius) * (this.radius + b.radius);
55+
}
56+
57+
void pop(Hashtable<Integer, Bubble> allBubbles) {
58+
System.out.println("Bubble " + this.id + " popped at (" + this.x + "," + this.y + ")!");
59+
allBubbles.remove(this.id);
60+
}
61+
62+
void handleCollision(ArrayList<Point> bubblesToCheck, Hashtable<Integer, Bubble> allBubbles) {
63+
boolean toBePopped = false; //if any other bubble collides with it, made true
64+
for (int i = 0; i < bubblesToCheck.size(); i++) {
65+
Integer otherId = bubblesToCheck.get(i).id;
66+
if (allBubbles.get(otherId) != null && //the bubble hasn't been popped yet
67+
this.id != otherId && //the two bubbles are not the same
68+
this.touches(allBubbles.get(otherId))) { //the bubbles touch
69+
allBubbles.get(otherId).pop(allBubbles);
70+
toBePopped = true;
71+
}
72+
}
73+
if (toBePopped) {
74+
this.pop(allBubbles);
75+
}
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* The MIT License
3+
* Copyright (c) 2014-2016 Ilkka Seppälä
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*/
23+
24+
package com.iluwatar.spatialpartition;
25+
26+
import java.util.ArrayList;
27+
import java.util.Hashtable;
28+
29+
/**
30+
* The abstract Point class which will be extended by any object in the field
31+
* whose location has to be kept track of. Defined by x,y coordinates and an id
32+
* for easy hashing into hashtable.
33+
* @param <T> T will be type subclass
34+
*/
35+
36+
public abstract class Point<T> {
37+
38+
public int x;
39+
public int y;
40+
public final int id;
41+
42+
Point(int x, int y, int id) {
43+
this.x = x;
44+
this.y = y;
45+
this.id = id;
46+
}
47+
48+
/**
49+
* defines how the object moves
50+
*/
51+
abstract void move();
52+
53+
/**
54+
* defines conditions for interacting with an object obj
55+
* @param obj is another object on field which also extends Point
56+
* @return whether the object can interact with the other or not
57+
*/
58+
abstract boolean touches(T obj);
59+
60+
/**
61+
* handling interactions/collisions with other objects
62+
* @param pointsToCheck contains the objects which need to be checked
63+
* @param allPoints contains hashtable of all points on field at this time
64+
*/
65+
abstract void handleCollision(ArrayList<Point> pointsToCheck, Hashtable<Integer, T> allPoints);
66+
}

0 commit comments

Comments
 (0)