Add CollisionMatrix and CollisionFlag.

This commit is contained in:
Major-
2015-02-28 03:41:29 +00:00
parent 94ba986f02
commit dc8b093bf3
4 changed files with 378 additions and 21 deletions
+47 -21
View File
@@ -8,7 +8,9 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apollo.game.model.Direction;
import org.apollo.game.model.Position;
import org.apollo.game.model.area.collision.CollisionMatrix;
import org.apollo.game.model.entity.Entity;
import org.apollo.game.model.entity.Entity.EntityType;
@@ -23,7 +25,7 @@ import com.google.common.collect.ImmutableSet;
public final class Sector {
/**
* The width and length of a sector, in tiles.
* The width and length of a Sector, in tiles.
*/
public static final int SECTOR_SIZE = 8;
@@ -33,20 +35,25 @@ public final class Sector {
private static final int DEFAULT_SET_SIZE = 2;
/**
* The sector coordinates of this sector.
* The SectorCoordinates of this Sector.
*/
private final SectorCoordinates coordinates;
/**
* A map of positions to entities in that position.
* The Map of Positions to Entities in that Position.
*/
private final Map<Position, Set<Entity>> entities = new HashMap<>();
/**
* A list of listeners registered to this sector.
* A List of SectorListeners registered to this Sector.
*/
private final List<SectorListener> listeners = new ArrayList<>();
/**
* The CollisionMatrix.
*/
private final CollisionMatrix matrix = new CollisionMatrix(SECTOR_SIZE, SECTOR_SIZE);
/**
* Creates a new sector.
*
@@ -67,28 +74,29 @@ public final class Sector {
}
/**
* Adds a {@link Entity} from to sector. Note that this does not spawn the entity, or do any other action other than
* Adds a {@link Entity} from to sector. Note that this does not spawn the Entity, or do any other action other than
* register it to this sector.
*
* @param entity The entity.
* @throws IllegalArgumentException If the entity does not belong in this sector.
* @param entity The Entity.
* @throws IllegalArgumentException If the Entity does not belong in this sector.
*/
public void addEntity(Entity entity) {
Position position = entity.getPosition();
checkPosition(position);
Set<Entity> local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
Set<Entity> local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
local.add(entity);
notifyListeners(entity, SectorOperation.ADD);
}
/**
* Checks if this sector contains the specified entity.
* Checks if this sector contains the specified Entity.
* <p>
* This method operates in constant time.
*
* @param entity The entity.
* @return {@code true} if this sector contains the entity, otherwise {@code false}.
* @param entity The Entity.
* @return {@code true} if this sector contains the Entity, otherwise {@code false}.
*/
public boolean contains(Entity entity) {
Position position = entity.getPosition();
@@ -114,12 +122,13 @@ public final class Sector {
* @return The list.
*/
public Set<Entity> getEntities(Position position) {
return ImmutableSet.copyOf(entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)));
Set<Entity> set = entities.get(position);
return (set == null) ? ImmutableSet.of() : ImmutableSet.copyOf(set);
}
/**
* Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}. The returned
* type will be immutable. Type will be inferred from the call, so ensure that the entity type and the reference
* type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference
* correspond, or this method will fail at runtime.
*
* @param position The {@link Position} containing the entities.
@@ -127,21 +136,24 @@ public final class Sector {
* @return The set of entities.
*/
public <T extends Entity> Set<T> getEntities(Position position, EntityType type) {
Set<Entity> local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
Set<Entity> local = entities.get(position);
if (local == null) {
return ImmutableSet.of();
}
@SuppressWarnings("unchecked")
Set<T> filtered = (Set<T>) local.stream().filter(entity -> entity.getEntityType() == type).collect(Collectors.toSet());
Set<T> filtered = (Set<T>) local.stream().filter(Entity -> Entity.getEntityType() == type).collect(Collectors.toSet());
return ImmutableSet.copyOf(filtered);
}
/**
* Moves the {@link Entity} that was in the specified {@code old} {@link Position}, to the current position of the
* entity.
* Entity.
* <p>
* Both the {@code old} and current positions of the entity must belong to this sector.
* Both the {@code old} and current positions of the Entity must belong to this sector.
*
* @param old The old position of the entity.
* @param entity The entity to move.
* @param old The old position of the Entity.
* @param entity The Entity to move.
* @throws IllegalArgumentException If either of the positions do not belong to this sector.
*/
public void moveEntity(Position old, Entity entity) {
@@ -175,8 +187,8 @@ public final class Sector {
/**
* Removes a {@link Entity} from this sector.
*
* @param entity The entity.
* @throws IllegalArgumentException If the entity does not belong in this sector, or if it was never added.
* @param entity The Entity.
* @throws IllegalArgumentException If the Entity does not belong in this sector, or if it was never added.
*/
public void removeEntity(Entity entity) {
Position position = entity.getPosition();
@@ -191,6 +203,20 @@ public final class Sector {
notifyListeners(entity, SectorOperation.REMOVE);
}
/**
* Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified
* coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param entity The {@link EntityType}.
* @param direction The {@link Direction} the Entity is approaching from.
* @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not.
*/
public boolean traversable(int x, int y, EntityType entity, Direction direction) {
return matrix.traversable(x, y, entity, direction);
}
/**
* Checks that the specified {@link Position} is included in this sector.
*
@@ -0,0 +1,100 @@
package org.apollo.game.model.area.collision;
/**
* A type of flag in a {@link CollisionMatrix}.
*
* @author Major
*/
public enum CollisionFlag {
/**
* The walk north flag.
*/
MOB_NORTH(0),
/**
* The walk east flag.
*/
MOB_EAST(1),
/**
* The walk south flag.
*/
MOB_SOUTH(2),
/**
* The walk west flag.
*/
MOB_WEST(3),
/**
* The projectile north flag.
*/
PROJECTILE_NORTH(4),
/**
* The projectile east flag.
*/
PROJECTILE_EAST(5),
/**
* The projectile south flag.
*/
PROJECTILE_SOUTH(6),
/**
* The projectile west flag.
*/
PROJECTILE_WEST(7);
/**
* Returns an array of CollisionFlags that indicate if a Mob can traverse over a tile.
*
* @return The array of CollisionFlags.
*/
public static CollisionFlag[] mobs() {
return new CollisionFlag[] { MOB_NORTH, MOB_EAST, MOB_SOUTH, MOB_WEST };
}
/**
* Returns an array of CollisionFlags that indicate if a Projectile can traverse over a tile.
*
* @return The array of CollisionFlags.
*/
public static CollisionFlag[] projectiles() {
return new CollisionFlag[] { PROJECTILE_NORTH, PROJECTILE_EAST, PROJECTILE_SOUTH, PROJECTILE_WEST };
}
/**
* The index of the bit this flag is stored in.
*/
private final int bit;
/**
* Creates the CollisionFlag.
*
* @param bit The index of the bit this flag is stored in.
*/
private CollisionFlag(int bit) {
this.bit = bit;
}
/**
* Gets this CollisionFlag, as a {@code byte}.
*
* @return The value, as a {@code byte}.
*/
public byte asByte() {
return (byte) (1 << bit);
}
/**
* Gets the index of the bit this flag is stored in.
*
* @return The index of the bit.
*/
public int getBit() {
return bit;
}
}
@@ -0,0 +1,227 @@
package org.apollo.game.model.area.collision;
import java.util.Arrays;
import org.apollo.game.model.Direction;
import org.apollo.game.model.entity.Entity.EntityType;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
/**
* A 2-dimensional adjacency matrix containing tile collision data.
*
* @author Major
*/
public final class CollisionMatrix {
/**
* Indicates that all types of traversal are allowed.
*/
private static final byte ALL_ALLOWED = 0b0000_0000;
/**
* Indicates that no types of traversal are allowed.
*/
private static final byte ALL_BLOCKED = (byte) 0b1111_1111;
/**
* The length of the matrix.
*/
private final int length;
/**
* The collision matrix, as a {@code byte} array.
*/
private final byte[] matrix;
/**
* The width of the matrix.
*/
private final int width;
/**
* Creates the CollisionMatrix.
*
* @param width The width of the matrix.
* @param length The length of the matrix.
*/
public CollisionMatrix(int width, int length) {
this.width = width;
this.length = length;
matrix = new byte[width * length];
}
/**
* Returns whether or not <strong>all</strong> of the specified {@link CollisionFlag}s are set for the specified
* coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param flags The CollisionFlags.
* @return {@code true} if all of the CollisionFlags are set, otherwise {@code false}.
*/
public boolean all(int x, int y, CollisionFlag... flags) {
for (CollisionFlag flag : flags) {
if ((get(x, y) & flag.asByte()) == 0) {
return false;
}
}
return true;
}
/**
* Returns whether or not <strong>any</strong> of the specified {@link CollisionFlag}s are set for the specified
* coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param flags The CollisionFlags.
* @return {@code true} if any of the CollisionFlags are set, otherwise {@code false}.
*/
public boolean any(int x, int y, CollisionFlag... flags) {
for (CollisionFlag flag : flags) {
if ((get(x, y) & flag.asByte()) != 0) {
return true;
}
}
return false;
}
/**
* Completely blocks the tile at the specified coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
*/
public void block(int x, int y) {
set(x, y, ALL_BLOCKED);
}
/**
* Clears (i.e. sets to {@code false}) the value of the specified {@link CollisionFlag} for the specified coordinate
* pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param flag The CollisionFlag.
*/
public void clear(int x, int y, CollisionFlag flag) {
set(x, y, (byte) ~flag.asByte());
}
/**
* Returns whether or not the specified {@link CollisionFlag} is set for the specified coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param flag The CollisionFlag.
* @return {@code true} if the CollisionFlag is set, {@code false} if not.
*/
public boolean flagged(int x, int y, CollisionFlag flag) {
return (get(x, y) & flag.asByte()) != 0;
}
/**
* Gets the value of the specified tile.
*
* @param x The x coordinate of the tile.
* @param y The y coordinate of the tile.
* @return The value.
*/
public int get(int x, int y) {
return matrix[indexOf(x, y)] & 0xFF;
}
/**
* Resets the cell of the specified coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
*/
public void reset(int x, int y) {
set(x, y, ALL_ALLOWED);
}
/**
* Sets (i.e. sets to {@code true}) the value of the specified {@link CollisionFlag} for the specified coordinate
* pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param flag The CollisionFlag.
*/
public void set(int x, int y, CollisionFlag flag) {
set(x, y, flag.asByte());
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("width", width).add("length", length).add("matrix", Arrays.toString(matrix))
.toString();
}
/**
* Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified
* coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param entity The {@link EntityType}.
* @param direction The {@link Direction} the Entity is approaching from.
* @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not.
*/
public boolean traversable(int x, int y, EntityType entity, Direction direction) {
CollisionFlag[] flags = (entity == EntityType.PROJECTILE) ? CollisionFlag.projectiles() : CollisionFlag.mobs();
int north = 0, east = 1, south = 2, west = 3;
switch (direction) {
case NORTH_WEST:
return any(x, y, flags[south], flags[east]);
case NORTH:
return flagged(x, y, flags[south]);
case NORTH_EAST:
return any(x, y, flags[south], flags[west]);
case EAST:
return flagged(x, y, flags[west]);
case SOUTH_EAST:
return any(x, y, flags[north], flags[west]);
case SOUTH:
return flagged(x, y, flags[north]);
case SOUTH_WEST:
return any(x, y, flags[north], flags[east]);
case WEST:
return flagged(x, y, flags[east]);
}
throw new IllegalArgumentException("Unrecognised direction " + direction + ".");
}
/**
* Gets the index in the matrix for the specified coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @return The index.
* @throws ArrayIndexOutOfBoundsException If the specified coordinate pair does not fit in this matrix.
*/
private int indexOf(int x, int y) {
int index = y * width + x;
Preconditions.checkElementIndex(index, matrix.length, "Index out of bounds.");
return index;
}
/**
* Sets the appropriate index for the specified coordinate pair to the specified value.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param value The value.
*/
private void set(int x, int y, byte value) {
matrix[indexOf(x, y)] = value;
}
}
@@ -0,0 +1,4 @@
/**
* Contains classes related to tile collision data.
*/
package org.apollo.game.model.area.collision;