提交 a6749cb6 编写于 作者: A AnaghaSasikumar 提交者: Ilkka Seppälä

Spatial partition pattern #562 (#828)

* Spatial partition

* Update pom.xml

* Update Bubble.java - pmd

* Update Rect.java - pmd
上级 7b8c9b07
......@@ -164,6 +164,7 @@
<module>ambassador</module>
<module>acyclic-visitor</module>
<module>collection-pipeline</module>
<module>spatial-partition</module>
</modules>
<repositories>
......
---
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
<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">
<!--
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.
-->
<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>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
/**
* 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");
}
}
/**
* 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);
}
}
}
/**
* 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);
}
/**
* 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;
/**
* The quadtree data structure is being used to keep track of the objects' locations.
* It has the insert(Point) and query(range) methods to insert a new object and find
* the objects within a certain (rectangular) range respectively.
*/
public class QuadTree {
Rect boundary;
int capacity;
boolean divided;
Hashtable<Integer, Point> points;
QuadTree northwest;
QuadTree northeast;
QuadTree southwest;
QuadTree southeast;
QuadTree(Rect boundary, int capacity) {
this.boundary = boundary;
this.capacity = capacity;
this.divided = false;
this.points = new Hashtable<Integer, Point>();
this.northwest = null;
this.northeast = null;
this.southwest = null;
this.southeast = null;
}
void insert(Point p) {
if (!this.boundary.contains(p)) {
return;
} else {
if (this.points.size() < this.capacity) {
points.put(p.id, p);
} else {
if (!this.divided) {
this.divide();
}
if (this.northwest.boundary.contains(p)) {
this.northwest.insert(p);
} else if (this.northeast.boundary.contains(p)) {
this.northeast.insert(p);
} else if (this.southwest.boundary.contains(p)) {
this.southwest.insert(p);
} else if (this.southeast.boundary.contains(p)) {
this.southeast.insert(p);
}
}
}
}
void divide() {
Rect nw = new Rect(this.boundary.x - this.boundary.width / 4, this.boundary.y + this.boundary.height / 4,
this.boundary.width / 2, this.boundary.height / 2);
this.northwest = new QuadTree(nw , this.capacity);
Rect ne = new Rect(this.boundary.x + this.boundary.width / 4, this.boundary.y + this.boundary.height / 4,
this.boundary.width / 2, this.boundary.height / 2);
this.northeast = new QuadTree(ne , this.capacity);
Rect sw = new Rect(this.boundary.x - this.boundary.width / 4, this.boundary.y - this.boundary.height / 4,
this.boundary.width / 2, this.boundary.height / 2);
this.southwest = new QuadTree(sw , this.capacity);
Rect se = new Rect(this.boundary.x + this.boundary.width / 4, this.boundary.y - this.boundary.height / 4,
this.boundary.width / 2, this.boundary.height / 2);
this.southeast = new QuadTree(se , this.capacity);
this.divided = true;
}
ArrayList<Point> query(Rect r, ArrayList<Point> relevantPoints) {
//could also be a circle instead of a rectangle
if (this.boundary.intersects(r)) {
for (Enumeration<Integer> e = this.points.keys(); e.hasMoreElements();) {
Integer i = e.nextElement();
if (r.contains(this.points.get(i))) {
relevantPoints.add(this.points.get(i));
}
}
if (this.divided) {
this.northwest.query(r, relevantPoints);
this.northeast.query(r, relevantPoints);
this.southwest.query(r, relevantPoints);
this.southeast.query(r, relevantPoints);
}
}
return relevantPoints;
}
}
/**
* 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;
/**
* The Rect class helps in defining the boundary of the quadtree and is also used to
* define the range within which objects need to be found in our example.
*/
public class Rect {
int x;
int y;
int width;
int height;
//(x,y) - centre of rectangle
Rect(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
boolean contains(Point p) {
return p.x >= this.x - this.width / 2 && p.x <= this.x + this.width / 2
&& p.y >= this.y - this.height / 2 && p.y <= this.y + this.height / 2;
}
boolean intersects(Rect other) {
return !(this.x + this.width / 2 <= other.x - other.width / 2
|| this.x - this.width / 2 >= other.x + other.width / 2
|| this.y + this.height / 2 <= other.y - other.height / 2
|| this.y - this.height / 2 >= other.y + other.height / 2);
}
}
/**
* 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;
/**
* This class extends the generic SpatialPartition abstract class and is used in
* our example to keep track of all the bubbles that collide, pop and stay un-popped.
*/
public class SpatialPartitionBubbles extends SpatialPartitionGeneric<Bubble> {
Hashtable<Integer, Bubble> bubbles;
QuadTree qTree;
SpatialPartitionBubbles(Hashtable<Integer, Bubble> bubbles, QuadTree qTree) {
this.bubbles = bubbles;
this.qTree = qTree;
}
void handleCollisionsUsingQt(Bubble b) {
//finding points within area of a square drawn with centre same as centre of bubble and length = radius of bubble
Rect rect = new Rect(b.x, b.y, 2 * b.radius, 2 * b.radius);
ArrayList<Point> quadTreeQueryResult = new ArrayList<Point>();
this.qTree.query(rect, quadTreeQueryResult);
//handling these collisions
b.handleCollision(quadTreeQueryResult, this.bubbles);
}
}
/**
* 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.Hashtable;
/**
* This abstract class has 2 fields, one of which is a hashtable containing all objects
* that currently exist on the field and a quadtree which keeps track of locations.
* @param <T> T will be type of object (that extends Point)
*/
public abstract class SpatialPartitionGeneric<T> {
Hashtable<Integer, T> playerPositions;
QuadTree qTree;
/**
* handles collisions for object obj using quadtree
* @param obj is the object for which collisions need to be checked
*/
abstract void handleCollisionsUsingQt(T obj);
}
/**
* 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 static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.Hashtable;
import org.junit.jupiter.api.Test;
/**
* Testing methods in Bubble class.
*/
class BubbleTest {
@Test
void moveTest() {
Bubble b = new Bubble(10,10,1,2);
int initialX = b.x;
int initialY = b.y;
b.move();
//change in x and y < |2|
assertTrue((b.x - initialX < 2 && b.x - initialX > -2) && (b.y - initialY < 2 && b.y - initialY > -2));
}
@Test
void touchesTest() {
Bubble b1 = new Bubble(0,0,1,2);
Bubble b2 = new Bubble(1,1,2,1);
Bubble b3 = new Bubble(10,10,3,1);
//b1 touches b2 but not b3
assertTrue(b1.touches(b2) && !b1.touches(b3));
}
@Test
void popTest() {
Bubble b1 = new Bubble(10,10,1,2);
Bubble b2 = new Bubble(0,0,2,2);
Hashtable<Integer, Bubble> bubbles = new Hashtable<Integer, Bubble>();
bubbles.put(1, b1);
bubbles.put(2, b2);
b1.pop(bubbles);
//after popping, bubble no longer in hashtable containing all bubbles
assertTrue(bubbles.get(1) == null && bubbles.get(2) != null);
}
@Test
void handleCollisionTest() {
Bubble b1 = new Bubble(0,0,1,2);
Bubble b2 = new Bubble(1,1,2,1);
Bubble b3 = new Bubble(10,10,3,1);
Hashtable<Integer, Bubble> bubbles = new Hashtable<Integer, Bubble>();
bubbles.put(1, b1);
bubbles.put(2, b2);
bubbles.put(3, b3);
ArrayList<Point> bubblesToCheck = new ArrayList<Point>();
bubblesToCheck.add(b2);
bubblesToCheck.add(b3);
b1.handleCollision(bubblesToCheck, bubbles);
//b1 touches b2 and not b3, so b1, b2 will be popped
assertTrue(bubbles.get(1) == null && bubbles.get(2) == null && bubbles.get(3) != null);
}
}
/**
* 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 static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Random;
import org.junit.jupiter.api.Test;
/**
* Testing QuadTree class.
*/
class QuadTreeTest {
@Test
void queryTest() {
ArrayList<Point> points = new ArrayList<Point>();
Random rand = new Random();
for (int i = 0; i < 20; i++) {
Bubble p = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1);
points.add(p);
}
Rect field = new Rect(150,150,300,300); //size of field
Rect queryRange = new Rect(70,130,100,100); //result = all points lying in this rectangle
//points found in the query range using quadtree and normal method is same
assertTrue(QuadTreeTest.quadTreeTest(points, field, queryRange).equals(QuadTreeTest.verify(points, queryRange)));
}
static Hashtable<Integer, Point> quadTreeTest(ArrayList<Point> points, Rect field, Rect queryRange) {
//creating quadtree and inserting all points
QuadTree qTree = new QuadTree(field, 4);
for (int i = 0; i < points.size(); i++) {
qTree.insert(points.get(i));
}
ArrayList<Point> queryResult = qTree.query(queryRange, new ArrayList<Point>());
Hashtable<Integer, Point> result = new Hashtable<Integer, Point>();
for (int i = 0; i < queryResult.size(); i++) {
Point p = queryResult.get(i);
result.put(p.id, p);
}
return result;
}
static Hashtable<Integer, Point> verify(ArrayList<Point> points, Rect queryRange) {
Hashtable<Integer, Point> result = new Hashtable<Integer, Point>();
for (int i = 0; i < points.size(); i++) {
if (queryRange.contains(points.get(i))) {
result.put(points.get(i).id, points.get(i));
}
}
return result;
}
}
/**
* 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 static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* Testing Rect class.
*/
class RectTest {
@Test
void containsTest() {
Rect r = new Rect(10,10,20,20);
Bubble b1 = new Bubble(2,2,1,1);
Bubble b2 = new Bubble(30,30,2,1);
//r contains b1 and not b2
assertTrue(r.contains(b1) && !r.contains(b2));
}
@Test
void intersectsTest() {
Rect r1 = new Rect(10,10,20,20);
Rect r2 = new Rect(15,15,20,20);
Rect r3 = new Rect(50,50,20,20);
//r1 intersects r2 and not r3
assertTrue(r1.intersects(r2) && !r1.intersects(r3));
}
}
/**
* 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 static org.junit.jupiter.api.Assertions.*;
import java.util.Hashtable;
import org.junit.jupiter.api.Test;
/**
* Testing SpatialPartition_Bubbles class.
*/
class SpatialPartitionBubblesTest {
@Test
void handleCollisionsUsingQtTest() {
Bubble b1 = new Bubble(10,10,1,3);
Bubble b2 = new Bubble(5,5,2,1);
Bubble b3 = new Bubble(9,9,3,1);
Bubble b4 = new Bubble(8,8,4,2);
Hashtable<Integer, Bubble> bubbles = new Hashtable<Integer, Bubble>();
bubbles.put(1, b1);
bubbles.put(2, b2);
bubbles.put(3, b3);
bubbles.put(4, b4);
Rect r = new Rect(10,10,20,20);
QuadTree qt = new QuadTree(r,4);
qt.insert(b1);
qt.insert(b2);
qt.insert(b3);
qt.insert(b4);
SpatialPartitionBubbles sp = new SpatialPartitionBubbles(bubbles, qt);
sp.handleCollisionsUsingQt(b1);
//b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped
assertTrue(bubbles.get(1) == null && bubbles.get(2) != null && bubbles.get(3) == null && bubbles.get(4) == null);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册