package com.supaham.commons.bukkit.area;
import com.google.common.base.Preconditions;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.supaham.commons.bukkit.serializers.CBSerializers.ListVectorSerializer;
import com.supaham.commons.bukkit.utils.LocationUtils;
import org.bukkit.Location;
import org.bukkit.util.Vector;
import java.util.Collection;
import java.util.Iterator;
import javax.annotation.Nonnull;
import pluginbase.config.annotation.SerializableAs;
import pluginbase.config.annotation.SerializeWith;
/**
* Represents a serializable 2D polygonal object using a collection of points to define the region
* followed by a min & max y coordinate to define height.
*
* @see #fromLocations(Collection, int, int)
* @see #Poly2DRegion(Collection, int, int)
* @since 0.2.3
*/
@SerializableAs("Poly2DRegion")
public class Poly2DRegion implements Region {
@SerializeWith(ListVectorSerializer.class)
private Collection<Vector> points;
private Vector min = new Vector(0, 0, 0);
private Vector max = new Vector(0, 0, 0);
/**
* Constructs a 2D polygonal region out of a collection of {@link Location}s and a min & max y
* coordinate. This is equivalent to calling {@link #Poly2DRegion(Collection, int, int)} with
* the collection transformed to {@link LocationUtils#toVectorFunction()}.
*
* @param points points to define the polygonal region
* @param minY minimum level on the y axis
* @param maxY maximum level on the y axis
*
* @return 2D polygonal region
*
* @see #Poly2DRegion(Collection, int, int)
*/
public static Poly2DRegion fromLocations(@Nonnull Collection<Location> points, int minY,
int maxY) {
Preconditions.checkNotNull(points, "first point cannot be null.");
Preconditions.checkArgument(minY >= 0, "minY cannot be smaller than 0.");
Preconditions.checkArgument(maxY >= 0, "maxY cannot be smaller than 0.");
return new Poly2DRegion(Collections2.transform(points, LocationUtils.toVectorFunction()),
minY, maxY);
}
protected Poly2DRegion() {}
/**
* Constructs a 2D polygonal region out of a collection of {@link Vector}s and a min & max y
* coordinate.
*
* @param points points to define the polygonal region
* @param minY minimum level on the y axis
* @param maxY maximum level on the y axis
*
* @see #fromLocations(Collection, int, int)
*/
public Poly2DRegion(@Nonnull Collection<Vector> points, int minY, int maxY) {
Preconditions.checkNotNull(points, "points cannot be null.");
Preconditions.checkArgument(minY >= 0, "minY cannot be smaller than 0.");
Preconditions.checkArgument(maxY >= 0, "maxY cannot be smaller than 0.");
this.points = points;
Iterator<Vector> it = points.iterator();
int minX = Iterables.get(points, 0).getBlockX();
int minZ = Iterables.get(points, 0).getBlockZ();
int maxX = minX;
int maxZ = minZ;
while (it.hasNext()) {
Vector next = it.next();
int x = next.getBlockX();
int z = next.getBlockZ();
if (x < minX) {
minX = x;
} else if (x > maxX) {
maxX = x;
}
if (z < minZ) {
minZ = z;
} else if (z > maxZ) {
maxZ = z;
}
}
}
@Override public Vector getMinimumPoint() {
return this.min;
}
@Override public Vector getMaximumPoint() {
return this.max;
}
/*
* This piece of code was taken from Sk89q's WorldEdit project licensed under GNU GPL v3, and
* slightly modified.
* https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java
*/
@Override public boolean contains(@Nonnull Vector vector) {
if (this.points.size() < 3) {
return false;
}
int targetX = vector.getBlockX(); //wide
int targetY = vector.getBlockY(); //height
int targetZ = vector.getBlockZ(); //depth
if (targetY < this.min.getBlockY() || targetY > this.max.getBlockY()) {
return false;
}
boolean inside = false;
int npoints = this.points.size();
int xNew, zNew;
int xOld, zOld;
int x1, z1;
int x2, z2;
long crossproduct;
int i;
xOld = Iterables.get(this.points, npoints - 1).getBlockX();
zOld = Iterables.get(this.points, npoints - 1).getBlockZ();
for (Vector point : this.points) {
xNew = point.getBlockX();
zNew = point.getBlockZ();
//Check for corner
if (xNew == targetX && zNew == targetZ) {
return true;
}
if (xNew > xOld) {
x1 = xOld;
x2 = xNew;
z1 = zOld;
z2 = zNew;
} else {
x1 = xNew;
x2 = xOld;
z1 = zNew;
z2 = zOld;
}
if (x1 <= targetX && targetX <= x2) {
crossproduct = ((long) targetZ - (long) z1) * (long) (x2 - x1)
- ((long) z2 - (long) z1) * (long) (targetX - x1);
if (crossproduct == 0) {
if ((z1 <= targetZ) == (targetZ <= z2)) {
return true; //on edge
}
} else if (crossproduct < 0 && (x1 != targetX)) {
inside = !inside;
}
}
xOld = xNew;
zOld = zNew;
}
return inside;
}
@Override public Iterator<Vector> iterator() {
return null; // TODO implement
}
}