From 2d950117d3d232b4a3259daed8ad61b785135dd5 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Mon, 14 Aug 2023 00:43:01 +0200 Subject: [PATCH] New BlockSet super-interface for AABBBlock and AABBBlockGroup + reorganized classes related to geometry to a new package --- .../lib/paper/geometry/DirectionalVector.java | 218 ++++++++++ .../lib/paper/geometry/GeometryUtil.java | 155 +++++++ .../{util => geometry/blocks}/AABBBlock.java | 71 ++-- .../paper/geometry/blocks/AABBBlockGroup.java | 138 ++++++ .../lib/paper/geometry/blocks/BlockSet.java | 64 +++ .../lib/paper/util/AABBBlockGroup.java | 59 --- .../lib/paper/util/GeometryUtil.java | 395 ------------------ 7 files changed, 621 insertions(+), 479 deletions(-) create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/DirectionalVector.java create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/GeometryUtil.java rename pandalib-paper/src/main/java/fr/pandacube/lib/paper/{util => geometry/blocks}/AABBBlock.java (75%) create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlockGroup.java create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/BlockSet.java delete mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlockGroup.java delete mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/DirectionalVector.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/DirectionalVector.java new file mode 100644 index 0000000..fc03511 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/DirectionalVector.java @@ -0,0 +1,218 @@ +package fr.pandacube.lib.paper.geometry; + +import org.bukkit.Location; +import org.bukkit.util.Vector; + +/** + * This vector considers Minecraft X Y Z axis orientation, + * but consider standard (not Minecraft) radian values for yaw and pitch.
+ * The length of this Vector (based on {@link #x}, {@link #y} and {@link #z} values) + * Is always 1. + * + *
Yaw :
+ * North (-z) = -PI/2
+ * East  (+x) = 0
+ * South (+z) = PI/2
+ * West  (-x) = ±PI
+ *
+ * Pitch :
+ * Up   (+y) = PI/2
+ * Down (-y) = -PI/2
+ */ +public class DirectionalVector { + /** + * The X cartesian coordinate of this {@link DirectionalVector}. + * It corresponds to the X (west to east) axis in a Minecraft world. + */ + public final double x; + + /** + * The Y cartesian coordinate of this {@link DirectionalVector}. + * It corresponds to the Y (bottom to top) axis in a Minecraft world. + */ + public final double y; + + /** + * The Z cartesian coordinate of this {@link DirectionalVector}. + * It corresponds to the Z (north to south) axis in a Minecraft world. + */ + public final double z; + + /** + * The azimuthal angle φ (phi) of this {@link DirectionalVector}, in radian. + * It corresponds with Minecraft world as follows : + *
Yaw :
+     * North (-z) = -PI/2
+     * East  (+x) = 0
+     * South (+z) = PI/2
+     * West  (-x) = ±PI
+ */ + public final double yaw; + + /** + * The polar angle θ (theta) of this {@link DirectionalVector}, in radian. + * It corresponds with Minecraft world as follows : + *
Pitch :
+     * Down (-y) = -PI/2
+     * Up   (+y) = PI/2
+ */ + public final double pitch; + + /** + * Initialize this {@link DirectionalVector} with the yaw and pitch + * contained in the provided {@link Location}. + * {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically + * converted to conform {@link #yaw} and {@link #pitch} specification. + */ + public DirectionalVector(Location l) { + this( + Math.toRadians(((l.getYaw() + 90) % 360) > 180 ? ((l.getYaw() + 90) % 360) - 360 : ((l.getYaw() + 90) % 360)), + -Math.toRadians(l.getPitch()) + ); + /* MC : +90 : %360 : >180 -> -360 + * South (+z) = 0, 360 : 90-450 : 90 : 90 : PI/2 + * West (-x) = 90 : 180 : 180 : ±180 : ±PI + * North (-z) = 180 : 270 : 270 : -90 : -PI/2 + * East (+x) = 270 : 360 : 0-360 : 0 : 0 + */ + } + + + /** + * @param v the vector representing the direction. If v.getX() and v.getZ() are 0, + * the yaw will be 0. This may have inconsistency if the vector is calculated + * from a {@link Location}'s yaw and pitch. In this case, prefer using + * {@link #DirectionalVector(Location)}. The {@link Vector} is + * normalized if necessary (does not modify the provided {@link Vector}). + */ + public DirectionalVector(Vector v) { + this(v.getX(), v.getY(), v.getZ()); + // this((v = v.clone().normalize()).getX(), v.getY(), v.getZ()); + } + + + private DirectionalVector(double x, double y, double z) { + double vecSize = Math.sqrt(x * x + y * y + z * z); + this.x = x / vecSize; + this.y = y / vecSize; + this.z = z / vecSize; + + if (x == 0.0 && z == 0.0) { + pitch = y > 0.0 ? GeometryUtil.PId2 : -GeometryUtil.PId2; + yaw = 0; + } else { + yaw = Math.atan2(z, x); + pitch = Math.atan(y / Math.sqrt(x * x + z * z)); + } + } + + private DirectionalVector(double x, double y, double z, double yaw, double pitch) { + this.x = x; + this.y = y; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + } + + private DirectionalVector(double yaw, double pitch) { + this.yaw = yaw; + this.pitch = pitch; + + y = Math.sin(pitch); + + double cosPitch = Math.cos(pitch); + x = cosPitch * Math.cos(yaw); + z = cosPitch * Math.sin(yaw); + + } + + + public Vector toVector() { + return new Vector(x, y, z); + } + + + /** + * Set the yaw and the pitch of the provided {@link Location} + * with the values inside the current {@link DirectionalVector} + * after conversion of these values + */ + public void putIntoLocation(Location l) { + /* std : -PI/2 : <0 ? +2PI : MC + * South (+z) = PI/2 : 0 : 0 : 0, 360 + * West (-x) = ±PI : -3PI/2 - PI/2 : PI/2 : 90 + * North (-z) = -PI/2 : -PI : PI : 180 + * East (+x) = 0 : -PI/2 : 3PI/2 : 270 + */ + l.setYaw((float) Math.toDegrees(yaw < GeometryUtil.PId2 ? yaw + GeometryUtil.PIx2 - GeometryUtil.PId2 : yaw - GeometryUtil.PId2)); + l.setPitch((float) Math.toDegrees(-pitch)); + } + + + public DirectionalVector getOpposite() { + return new DirectionalVector( + -x, + -y, + -z, + (yaw > 0 ? (yaw - GeometryUtil.PI) : (yaw + GeometryUtil.PI)), + -pitch + ); + } + + /** + * If the current direction is the player face direction, + * this method return the direction of the back of the head. + * This is an alias of {@link #getOpposite()} + */ + public DirectionalVector getBackDirection() { + return getOpposite(); + } + + /** + * If the current direction is the player face direction, + * this method return the direction of the bottom of the head. + */ + public DirectionalVector getBottomDirection() { + return new DirectionalVector( + (pitch > 0 ? yaw : (yaw > 0 ? (yaw - GeometryUtil.PI) : (yaw + GeometryUtil.PI))), + (pitch > 0 ? (pitch - GeometryUtil.PId2) : (-GeometryUtil.PId2 - pitch)) + ); + } + + /** + * If the current direction is the player face direction, + * this method return the direction of the top of the head. + */ + public DirectionalVector getTopDirection() { + return new DirectionalVector( + (pitch < 0 ? yaw : (yaw > 0 ? (yaw - GeometryUtil.PI) : (yaw + GeometryUtil.PI))), + (pitch < 0 ? (pitch + GeometryUtil.PId2) : (GeometryUtil.PId2 - pitch)) + ); + } + + + /** + * If the current direction is the player face direction, + * this method return the direction of the left of the head. + */ + public DirectionalVector getLeftDirection() { + return new DirectionalVector( + yaw > -GeometryUtil.PId2 ? (yaw - GeometryUtil.PId2) : (yaw - GeometryUtil.PId2 + GeometryUtil.PIx2), + 0 + ); + } + + + /** + * If the current direction is the player face direction, + * this method return the direction of the right of the head. + */ + public DirectionalVector getRightDirection() { + return new DirectionalVector( + yaw < GeometryUtil.PId2 ? (yaw + GeometryUtil.PId2) : (yaw + GeometryUtil.PId2 - GeometryUtil.PIx2), + 0 + ); + } + + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/GeometryUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/GeometryUtil.java new file mode 100644 index 0000000..d58c62b --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/GeometryUtil.java @@ -0,0 +1,155 @@ +package fr.pandacube.lib.paper.geometry; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public class GeometryUtil { + + /** + * Value equal to {@link Math#PI}. + */ + public static final double PI = Math.PI; + + /** + * Value equal to {@link Math#PI} / 2. + */ + public static final double PId2 = PI/2; + + /** + * Value equal to {@link Math#PI} * 2. + */ + public static final double PIx2 = PI*2; + + + + + + /* + * Player geometry + */ + + + /** + * The visual height of a Minecraft player skin, when he is standing up and not sneaking, + * from the ground where the player is standing on, to the above of the first layer of the head skin. + * It doesn't correspond to the player hit box height.
+ *
+ * The value is provided in Minecraft Wiki. + */ + public static final double PLAYER_SKIN_HEIGHT = 1.85; + /** + * Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight + */ + public static final double PLAYER_EYE_HEIGHT = 1.62; + /** + * The visual height of a Minecraft player skin, when he is standing up and sneaking, + * from the ground where the player is standing on, to the above of the first layer of the head skin. + * It may not correspond to the player hit box height.
+ *
+ * The current value is the height of the player's hit box when sneaking. Even if this + * is close to the real value (tested in game), this is not the exact value. + */ + public static final double PLAYER_SKIN_HEIGHT_SNEAK = 1.50; + /** + * Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight + */ + public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27; + public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32; + public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24; + public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK); + public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8; + + + + + + + + + + + + + + + /** + * Get the {@link Location}s of the 8 vertex of the player head
+ * This method only work if the player is standing up + * (not dead, not gliding, not sleeping). + * @param playerLocation the location of the player, generally provided by {@link Player#getLocation()} + * @param isSneaking if the player is sneaking. Generally {@link Player#isSneaking()} + * @return an array of 8 {@link Location}s with x, y, and z values filled (yaw and pitch are ignored). + *
+	 * return[0] // top front left
+	 * return[1] // top front right
+	 * return[2] // bottom front left
+	 * return[3] // bottom front right
+	 * return[4] // top back left
+	 * return[5] // top back right
+	 * return[6] // bottom back left
+	 * return[7] // bottom back right
+	 * 
+ */ + public static Location[] getPlayerHeadGeometry(Location playerLocation, boolean isSneaking) { + Location[] headAnglesPoints = new Location[8]; + + Location playerHeadRotationLocation = playerLocation.clone() + .add(0, isSneaking ? PLAYER_HEAD_ROTATION_HEIGHT_SNEAK : PLAYER_HEAD_ROTATION_HEIGHT, 0); + + DirectionalVector frontDirection = new DirectionalVector(playerHeadRotationLocation); + Vector frontHalfVector = frontDirection.toVector().multiply(PLAYER_HEAD_SIZE/2); + Vector backHalfDirection = frontDirection.getBackDirection().toVector().multiply(PLAYER_HEAD_SIZE/2); + Vector leftHalfVector = frontDirection.getLeftDirection().toVector().multiply(PLAYER_HEAD_SIZE/2); + Vector rightHalfVector = frontDirection.getRightDirection().toVector().multiply(PLAYER_HEAD_SIZE/2); + Vector topVector = frontDirection.getTopDirection().toVector().multiply(PLAYER_HEAD_SIZE); + + Location bottomFrontMiddle = playerHeadRotationLocation.clone().add(frontHalfVector); + Location bottomBackMiddle = playerHeadRotationLocation.clone().add(backHalfDirection); + + Location topFrontMiddle = bottomFrontMiddle.clone().add(topVector); + Location topBackMiddle = bottomBackMiddle.clone().add(topVector); + + headAnglesPoints[0] = topFrontMiddle.clone().add(leftHalfVector); + headAnglesPoints[1] = topFrontMiddle.clone().add(rightHalfVector); + headAnglesPoints[2] = bottomFrontMiddle.clone().add(leftHalfVector); + headAnglesPoints[3] = bottomFrontMiddle.clone().add(rightHalfVector); + headAnglesPoints[4] = topBackMiddle.clone().add(leftHalfVector); + headAnglesPoints[5] = topBackMiddle.clone().add(rightHalfVector); + headAnglesPoints[6] = bottomBackMiddle.clone().add(leftHalfVector); + headAnglesPoints[7] = bottomBackMiddle.clone().add(rightHalfVector); + + return headAnglesPoints; + } + + + + + + /** + * Check if the path from start location to end pass through + * the axis aligned bounding box defined by min and max. + */ + public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) { + final double epsilon = 0.0001f; + + Vector d = end.clone().subtract(start).multiply(0.5); + Vector e = max.clone().subtract(min).multiply(0.5); + Vector c = start.clone().add(d).subtract(min.clone().add(max).multiply(0.5)); + Vector ad = d.clone(); + ad.setX(Math.abs(ad.getX())); + ad.setY(Math.abs(ad.getY())); + ad.setZ(Math.abs(ad.getZ())); + + return !( + Math.abs(c.getX()) > e.getX() + ad.getX() + || Math.abs(c.getY()) > e.getY() + ad.getY() + || Math.abs(c.getZ()) > e.getX() + ad.getZ() + || Math.abs(d.getY() * c.getZ() - d.getZ() * c.getY()) > e.getY() * ad.getZ() + e.getZ() * ad.getY() + epsilon + || Math.abs(d.getZ() * c.getX() - d.getX() * c.getZ()) > e.getZ() * ad.getX() + e.getX() * ad.getZ() + epsilon + || Math.abs(d.getX() * c.getY() - d.getY() * c.getX()) > e.getX() * ad.getY() + e.getY() * ad.getX() + epsilon + ); + } + + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlock.java similarity index 75% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlock.java index 94e3c7b..fc5bc67 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlock.java @@ -1,29 +1,26 @@ -package fr.pandacube.lib.paper.util; - -import java.util.Iterator; +package fr.pandacube.lib.paper.geometry.blocks; +import fr.pandacube.lib.util.RandomUtil; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; -import org.bukkit.entity.Entity; import org.bukkit.util.BlockVector; import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; -import fr.pandacube.lib.util.RandomUtil; +import java.util.Iterator; /** - * Checkpoint represented as a 3D Axis and Block Aligned Bounding Box (sort of AABB). + * Block Aligned Bounding Box (sort of AABB). * Represent the littlest cuboid selection of blocks that contains the bounding box * passed to the constructor. */ -public class AABBBlock implements Iterable, Cloneable { +public class AABBBlock implements BlockSet, Cloneable { - public final Vector pos1, pos2; - + /* package */ final Vector pos1, pos2; private final Vector center; - private final long volume; + private BoundingBox bukkitBoundingBox; private AABBBlock(AABBBlock original, int shiftX, int shiftY, int shiftZ) { Vector shiftVec = new Vector(shiftX, shiftY, shiftZ); @@ -32,10 +29,18 @@ public class AABBBlock implements Iterable, Cloneable { center = original.center.clone().add(shiftVec); volume = original.volume; } - + public AABBBlock(Vector p1, Vector p2) { this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ()); } + + public AABBBlock(BoundingBox bb) { + pos1 = bb.getMin(); + pos2 = bb.getMax(); + center = bb.getCenter(); + volume = (int) bb.getVolume(); + bukkitBoundingBox = bb; + } public AABBBlock(Location l1, Location l2) { this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ()); @@ -64,6 +69,11 @@ public class AABBBlock implements Iterable, Cloneable { volume = (long) Math.abs(p2x_ - p1x_) * Math.abs(p2x_ - p1x_) * Math.abs(p2x_ - p1x_); } + @Override + public AABBBlock getEnglobingAABB() { + return this; + } + public AABBBlock shift(int x, int y, int z) { return new AABBBlock(this, x, y, z); } @@ -74,22 +84,14 @@ public class AABBBlock implements Iterable, Cloneable { return new AABBBlock(this, 0, 0, 0); } - public boolean overlaps(Entity e) { - return overlaps(e.getBoundingBox()); - } + public boolean overlaps(BoundingBox bb) { return asBukkitBoundingBox().overlaps(bb); } - + public boolean isInside(Vector v) { - return v.isInAABB(pos1, pos2); - } - public boolean isInside(Location l) { - return isInside(l.toVector()); - } - public boolean isInside(Entity p) { - return isInside(p.getLocation()); + return asBukkitBoundingBox().contains(v); } public Vector getCenter() { @@ -101,8 +103,11 @@ public class AABBBlock implements Iterable, Cloneable { } public BoundingBox asBukkitBoundingBox() { - return new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(), - pos2.getX(), pos2.getY(), pos2.getZ()); + if (bukkitBoundingBox == null) { + bukkitBoundingBox = new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(), + pos2.getX(), pos2.getY(), pos2.getZ()); + } + return bukkitBoundingBox; } public Vector getRandomPosition() { @@ -157,6 +162,22 @@ public class AABBBlock implements Iterable, Cloneable { } }; } - + + + + + static boolean overlap(AABBBlock aabb1, AABBBlock aabb2) { + return aabb1.asBukkitBoundingBox().overlaps(aabb2.asBukkitBoundingBox()); + } + + static boolean overlap(AABBBlock aabb1, BlockSet bs) { + if (!overlap(aabb1, bs.getEnglobingAABB())) + return false; + AABBBlock intersection = new AABBBlock(aabb1.asBukkitBoundingBox().intersection(bs.getEnglobingAABB().asBukkitBoundingBox())); + for (BlockVector bv : intersection) + if (bs.isInside(bv)) + return true; + return false; + } } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlockGroup.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlockGroup.java new file mode 100644 index 0000000..289400e --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/AABBBlockGroup.java @@ -0,0 +1,138 @@ +package fr.pandacube.lib.paper.geometry.blocks; + +import fr.pandacube.lib.util.IteratorIterator; +import fr.pandacube.lib.util.RandomUtil; +import org.bukkit.util.BlockVector; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class AABBBlockGroup implements BlockSet { + + public final List subAABB; + + private final AABBBlock englobingAABB; + + public AABBBlockGroup(Collection in) { + if (in.isEmpty()) + throw new IllegalArgumentException("Provided collection must not be empty."); + subAABB = List.copyOf(in); + englobingAABB = initEnglobingAABB(); + } + + public AABBBlockGroup(AABBBlock... in) { + this(Arrays.asList(in)); + } + + private AABBBlock initEnglobingAABB() { + Vector pos1 = subAABB.get(0).pos1.clone(); + Vector pos2 = subAABB.get(0).pos2.clone().add(new Vector(-1, -1, -1)); + for (int i = 1; i < subAABB.size(); i++) { + AABBBlock aabb = subAABB.get(i); + pos1.setX(Math.min(pos1.getBlockX(), aabb.pos1.getBlockX())); + pos1.setY(Math.min(pos1.getBlockY(), aabb.pos1.getBlockZ())); + pos1.setZ(Math.min(pos1.getBlockY(), aabb.pos1.getBlockZ())); + pos2.setX(Math.max(pos2.getBlockX(), aabb.pos2.getBlockX() - 1)); + pos2.setY(Math.max(pos2.getBlockY(), aabb.pos2.getBlockZ() - 1)); + pos2.setZ(Math.max(pos2.getBlockY(), aabb.pos2.getBlockZ() - 1)); + } + return new AABBBlock(pos1, pos2); + } + + @Override + public AABBBlock getEnglobingAABB() { + return englobingAABB; + } + + public boolean isInside(Vector v) { + if (!englobingAABB.isInside(v)) + return false; + for (AABBBlock b : subAABB) + if (b.isInside(v)) + return true; + return false; + } + + public Vector getRandomPosition() { + double[] freq = subAABB.stream().mapToDouble(AABBBlock::getVolume).toArray(); + int i = RandomUtil.randomIndexOfFrequencies(freq); + return subAABB.get(i).getRandomPosition(); + } + + public long getVolume() { + long v = 0; + for (AABBBlock b : subAABB) + v += b.getVolume(); + return v; + } + + @Override + public boolean overlaps(BoundingBox bb) { + if (!englobingAABB.overlaps(bb)) + return false; + for (AABBBlock b : subAABB) + if (b.overlaps(bb)) + return true; + return false; + } + + @Override + public Iterator iterator() { + return IteratorIterator.ofCollectionOfIterator(subAABB.stream().map(AABBBlock::iterator).toList()); + } + + + + + + /* package */ static boolean overlap(AABBBlockGroup aabbGroup, AABBBlock aabb) { + if (!aabbGroup.englobingAABB.overlaps(aabb)) + return false; + for (AABBBlock b : aabbGroup.subAABB) + if (b.overlaps(aabb)) + return true; + return false; + } + + /* package */ static boolean overlap(AABBBlockGroup aabbGroup1, AABBBlockGroup aabbGroup2) { + if (!aabbGroup1.englobingAABB.overlaps(aabbGroup2.englobingAABB)) + return false; + List group1SubList = new ArrayList<>(); + for (AABBBlock b : aabbGroup1.subAABB) { + if (b.overlaps(aabbGroup2.englobingAABB)) + group1SubList.add(b); + } + if (group1SubList.isEmpty()) + return false; + List group2SubList = new ArrayList<>(); + for (AABBBlock b : aabbGroup2.subAABB) { + if (b.overlaps(aabbGroup1.englobingAABB)) + group2SubList.add(b); + } + if (group2SubList.isEmpty()) + return false; + for (AABBBlock b1 : group1SubList) + for (AABBBlock b2 : group2SubList) + if (b1.overlaps(b2)) + return true; + return false; + } + + + + static boolean overlap(AABBBlockGroup aabbGroup, BlockSet bs) { + if (!aabbGroup.englobingAABB.overlaps(bs.getEnglobingAABB())) + return false; + for (AABBBlock b : aabbGroup.subAABB) { + if (b.overlaps(bs)) // already checks for englobingAABB before checking block per block + return true; + } + return false; + } + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/BlockSet.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/BlockSet.java new file mode 100644 index 0000000..3b00fc7 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/geometry/blocks/BlockSet.java @@ -0,0 +1,64 @@ +package fr.pandacube.lib.paper.geometry.blocks; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.util.BlockVector; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +public interface BlockSet extends Iterable { + + Vector getRandomPosition(); + long getVolume(); + AABBBlock getEnglobingAABB(); + + + boolean overlaps(BoundingBox bb); + default boolean overlaps(Entity e) { + return overlaps(e.getBoundingBox()); + } + default boolean overlaps(BlockSet bs) { + if (this instanceof AABBBlock b1) { + if (bs instanceof AABBBlock b2) + return AABBBlock.overlap(b1, b2); + if (bs instanceof AABBBlockGroup bg2) + return AABBBlockGroup.overlap(bg2, b1); + return AABBBlock.overlap(b1, bs); + } + if (this instanceof AABBBlockGroup bg1) { + if (bs instanceof AABBBlock b2) + return AABBBlockGroup.overlap(bg1, b2); + if (bs instanceof AABBBlockGroup bg2) + return AABBBlockGroup.overlap(bg1, bg2); + return AABBBlockGroup.overlap(bg1, bs); + } + return overlap(this, bs); + } + + + boolean isInside(Vector v); + default boolean isInside(Location l) { + return isInside(l.toVector()); + } + default boolean isInside(Block b) { + return isInside(b.getLocation().add(.5, .5, .5)); + } + default boolean isInside(Entity p) { + return isInside(p.getLocation()); + } + + + + + static boolean overlap(BlockSet bs1, BlockSet bs2) { + if (!bs1.getEnglobingAABB().overlaps(bs2.getEnglobingAABB())) + return false; + AABBBlock intersection = new AABBBlock(bs1.getEnglobingAABB().asBukkitBoundingBox().intersection(bs2.getEnglobingAABB().asBukkitBoundingBox())); + for (BlockVector bv : intersection) + if (bs1.isInside(bv) && bs2.isInside(bv)) + return true; + return false; + } + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlockGroup.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlockGroup.java deleted file mode 100644 index 933eb8e..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlockGroup.java +++ /dev/null @@ -1,59 +0,0 @@ -package fr.pandacube.lib.paper.util; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.util.BlockVector; -import org.bukkit.util.Vector; - -import fr.pandacube.lib.util.IteratorIterator; -import fr.pandacube.lib.util.RandomUtil; - -public class AABBBlockGroup implements Iterable { - - public final List aabbBlocks; - - public AABBBlockGroup(Collection in) { - aabbBlocks = List.copyOf(in); - } - - public AABBBlockGroup(AABBBlock... in) { - aabbBlocks = List.of(in); - } - - - public boolean isInside(Vector v) { - for (AABBBlock b : aabbBlocks) - if (b.isInside(v)) - return true; - return false; - } - public boolean isInside(Location l) { - return isInside(l.toVector()); - } - public boolean isInside(Entity p) { - return isInside(p.getLocation()); - } - - public Vector getRandomPosition() { - double[] freq = aabbBlocks.stream().mapToDouble(AABBBlock::getVolume).toArray(); - int i = RandomUtil.randomIndexOfFrequencies(freq); - return aabbBlocks.get(i).getRandomPosition(); - } - - public long getVolume() { - long v = 0; - for (AABBBlock b : aabbBlocks) - v += b.getVolume(); - return v; - } - - @Override - public Iterator iterator() { - return IteratorIterator.ofCollectionOfIterator(aabbBlocks.stream().map(AABBBlock::iterator).toList()); - } - -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java deleted file mode 100644 index e072da0..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GeometryUtil.java +++ /dev/null @@ -1,395 +0,0 @@ -package fr.pandacube.lib.paper.util; - -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -public class GeometryUtil { - - /** - * Value equal to {@link Math#PI}. - */ - public static final double PI = Math.PI; - - /** - * Value equal to {@link Math#PI} / 2. - */ - public static final double PId2 = PI/2; - - /** - * Value equal to {@link Math#PI} * 2. - */ - public static final double PIx2 = PI*2; - - - - - - /* - * Player geometry - */ - - - /** - * The visual height of a Minecraft player skin, when he is standing up and not sneaking, - * from the ground where the player is standing on, to the above of the first layer of the head skin. - * It doesn't correspond to the player hit box height.
- *
- * The value is provided in Minecraft Wiki. - */ - public static final double PLAYER_SKIN_HEIGHT = 1.85; - /** - * Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight - */ - public static final double PLAYER_EYE_HEIGHT = 1.62; - /** - * The visual height of a Minecraft player skin, when he is standing up and sneaking, - * from the ground where the player is standing on, to the above of the first layer of the head skin. - * It may not correspond to the player hit box height.
- *
- * The current value is the height of the player's hit box when sneaking. Even if this - * is close to the real value (tested in game), this is not the exact value. - */ - public static final double PLAYER_SKIN_HEIGHT_SNEAK = 1.50; - /** - * Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight - */ - public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27; - public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32; - public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24; - public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK); - public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8; - - - - - - - - - - - - - - - /** - * Get the {@link Location}s of the 8 vertex of the player head
- * This method only work if the player is standing up - * (not dead, not gliding, not sleeping). - * @param playerLocation the location of the player, generally provided by {@link Player#getLocation()} - * @param isSneaking if the player is sneaking. Generally {@link Player#isSneaking()} - * @return an array of 8 {@link Location}s with x, y, and z values filled (yaw and pitch are ignored). - *
-	 * return[0] // top front left
-	 * return[1] // top front right
-	 * return[2] // bottom front left
-	 * return[3] // bottom front right
-	 * return[4] // top back left
-	 * return[5] // top back right
-	 * return[6] // bottom back left
-	 * return[7] // bottom back right
-	 * 
- */ - public static Location[] getPlayerHeadGeometry(Location playerLocation, boolean isSneaking) { - Location[] headAnglesPoints = new Location[8]; - - Location playerHeadRotationLocation = playerLocation.clone() - .add(0, isSneaking ? PLAYER_HEAD_ROTATION_HEIGHT_SNEAK : PLAYER_HEAD_ROTATION_HEIGHT, 0); - - DirectionalVector frontDirection = new DirectionalVector(playerHeadRotationLocation); - Vector frontHalfVector = frontDirection.toVector().multiply(PLAYER_HEAD_SIZE/2); - Vector backHalfDirection = frontDirection.getBackDirection().toVector().multiply(PLAYER_HEAD_SIZE/2); - Vector leftHalfVector = frontDirection.getLeftDirection().toVector().multiply(PLAYER_HEAD_SIZE/2); - Vector rightHalfVector = frontDirection.getRightDirection().toVector().multiply(PLAYER_HEAD_SIZE/2); - Vector topVector = frontDirection.getTopDirection().toVector().multiply(PLAYER_HEAD_SIZE); - - Location bottomFrontMiddle = playerHeadRotationLocation.clone().add(frontHalfVector); - Location bottomBackMiddle = playerHeadRotationLocation.clone().add(backHalfDirection); - - Location topFrontMiddle = bottomFrontMiddle.clone().add(topVector); - Location topBackMiddle = bottomBackMiddle.clone().add(topVector); - - headAnglesPoints[0] = topFrontMiddle.clone().add(leftHalfVector); - headAnglesPoints[1] = topFrontMiddle.clone().add(rightHalfVector); - headAnglesPoints[2] = bottomFrontMiddle.clone().add(leftHalfVector); - headAnglesPoints[3] = bottomFrontMiddle.clone().add(rightHalfVector); - headAnglesPoints[4] = topBackMiddle.clone().add(leftHalfVector); - headAnglesPoints[5] = topBackMiddle.clone().add(rightHalfVector); - headAnglesPoints[6] = bottomBackMiddle.clone().add(leftHalfVector); - headAnglesPoints[7] = bottomBackMiddle.clone().add(rightHalfVector); - - return headAnglesPoints; - } - - - - - - /** - * Check if the path from start location to end pass through - * the axis aligned bounding box defined by min and max. - */ - public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) { - final double epsilon = 0.0001f; - - Vector d = end.clone().subtract(start).multiply(0.5); - Vector e = max.clone().subtract(min).multiply(0.5); - Vector c = start.clone().add(d).subtract(min.clone().add(max).multiply(0.5)); - Vector ad = d.clone(); - ad.setX(Math.abs(ad.getX())); - ad.setY(Math.abs(ad.getY())); - ad.setZ(Math.abs(ad.getZ())); - - return !( - Math.abs(c.getX()) > e.getX() + ad.getX() - || Math.abs(c.getY()) > e.getY() + ad.getY() - || Math.abs(c.getZ()) > e.getX() + ad.getZ() - || Math.abs(d.getY() * c.getZ() - d.getZ() * c.getY()) > e.getY() * ad.getZ() + e.getZ() * ad.getY() + epsilon - || Math.abs(d.getZ() * c.getX() - d.getX() * c.getZ()) > e.getZ() * ad.getX() + e.getX() * ad.getZ() + epsilon - || Math.abs(d.getX() * c.getY() - d.getY() * c.getX()) > e.getX() * ad.getY() + e.getY() * ad.getX() + epsilon - ); - } - - - - - - - - - - - - - - /** - * This vector considers Minecraft X Y Z axis orientation, - * but consider standard (not Minecraft) radian values for yaw and pitch.
- * The length of this Vector (based on {@link #x}, {@link #y} and {@link #z} values) - * Is always 1. - * - *
Yaw :
-	 * North (-z) = -PI/2
-	 * East  (+x) = 0
-	 * South (+z) = PI/2
-	 * West  (-x) = ±PI
-	 * 
-	 * Pitch :
-	 * Up   (+y) = PI/2
-	 * Down (-y) = -PI/2
- */ - public static class DirectionalVector { - /** - * The X cartesian coordinate of this {@link DirectionalVector}. - * It corresponds to the X (west to east) axis in a Minecraft world. - */ - public final double x; - - /** - * The Y cartesian coordinate of this {@link DirectionalVector}. - * It corresponds to the Y (bottom to top) axis in a Minecraft world. - */ - public final double y; - - /** - * The Z cartesian coordinate of this {@link DirectionalVector}. - * It corresponds to the Z (north to south) axis in a Minecraft world. - */ - public final double z; - - /** - * The azimuthal angle φ (phi) of this {@link DirectionalVector}, in radian. - * It corresponds with Minecraft world as follows : - *
Yaw :
-		 * North (-z) = -PI/2
-		 * East  (+x) = 0
-		 * South (+z) = PI/2
-		 * West  (-x) = ±PI
- */ - public final double yaw; - - /** - * The polar angle θ (theta) of this {@link DirectionalVector}, in radian. - * It corresponds with Minecraft world as follows : - *
Pitch :
-		 * Down (-y) = -PI/2
-		 * Up   (+y) = PI/2
- */ - public final double pitch; - - /** - * Initialize this {@link DirectionalVector} with the yaw and pitch - * contained in the provided {@link Location}. - * {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically - * converted to conform {@link #yaw} and {@link #pitch} specification. - */ - public DirectionalVector(Location l) { - this( - Math.toRadians(((l.getYaw()+90)%360) > 180 ? ((l.getYaw()+90)%360)-360 : ((l.getYaw()+90)%360)), - -Math.toRadians(l.getPitch()) - ); - /* MC : +90 : %360 : >180 -> -360 - * South (+z) = 0, 360 : 90-450 : 90 : 90 : PI/2 - * West (-x) = 90 : 180 : 180 : ±180 : ±PI - * North (-z) = 180 : 270 : 270 : -90 : -PI/2 - * East (+x) = 270 : 360 : 0-360 : 0 : 0 - */ - } - - - - /** - * @param v the vector representing the direction. If v.getX() and v.getZ() are 0, - * the yaw will be 0. This may have inconsistency if the vector is calculated - * from a {@link Location}'s yaw and pitch. In this case, prefer using - * {@link #DirectionalVector(Location)}. The {@link Vector} is - * normalized if necessary (does not modify the provided {@link Vector}). - */ - public DirectionalVector(Vector v) { - this(v.getX(), v.getY(), v.getZ()); - // this((v = v.clone().normalize()).getX(), v.getY(), v.getZ()); - } - - - - private DirectionalVector(double x, double y, double z) { - double vecSize = Math.sqrt(x*x + y*y + z*z); - this.x = x/vecSize; - this.y = y/vecSize; - this.z = z/vecSize; - - if (x == 0.0 && z == 0.0) { - pitch = y > 0.0 ? PId2 : -PId2; - yaw = 0; - } - else { - yaw = Math.atan2(z, x); - pitch = Math.atan(y / Math.sqrt(x*x + z*z)); - } - } - - private DirectionalVector(double x, double y, double z, double yaw, double pitch) { - this.x = x; - this.y = y; - this.z = z; - this.yaw = yaw; - this.pitch = pitch; - } - - private DirectionalVector(double yaw, double pitch) { - this.yaw = yaw; - this.pitch = pitch; - - y = Math.sin(pitch); - - double cosPitch = Math.cos(pitch); - x = cosPitch * Math.cos(yaw); - z = cosPitch * Math.sin(yaw); - - } - - - - - public Vector toVector() { - return new Vector(x, y, z); - } - - - /** - * Set the yaw and the pitch of the provided {@link Location} - * with the values inside the current {@link DirectionalVector} - * after conversion of these values - */ - public void putIntoLocation(Location l) { - /* std : -PI/2 : <0 ? +2PI : MC - * South (+z) = PI/2 : 0 : 0 : 0, 360 - * West (-x) = ±PI : -3PI/2 - PI/2 : PI/2 : 90 - * North (-z) = -PI/2 : -PI : PI : 180 - * East (+x) = 0 : -PI/2 : 3PI/2 : 270 - */ - l.setYaw((float)Math.toDegrees(yaw < PId2 ? yaw + PIx2 - PId2 : yaw - PId2)); - l.setPitch((float)Math.toDegrees(-pitch)); - } - - - - - public DirectionalVector getOpposite() { - return new DirectionalVector( - -x, - -y, - -z, - (yaw > 0 ? (yaw - PI) : (yaw + PI)), - -pitch - ); - } - - /** - * If the current direction is the player face direction, - * this method return the direction of the back of the head. - * This is an alias of {@link #getOpposite()} - */ - public DirectionalVector getBackDirection() { - return getOpposite(); - } - - /** - * If the current direction is the player face direction, - * this method return the direction of the bottom of the head. - */ - public DirectionalVector getBottomDirection() { - return new DirectionalVector( - (pitch > 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))), - (pitch > 0 ? (pitch - PId2) : (-PId2 - pitch)) - ); - } - - /** - * If the current direction is the player face direction, - * this method return the direction of the top of the head. - */ - public DirectionalVector getTopDirection() { - return new DirectionalVector( - (pitch < 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))), - (pitch < 0 ? (pitch + PId2) : (PId2 - pitch)) - ); - } - - - - /** - * If the current direction is the player face direction, - * this method return the direction of the left of the head. - */ - public DirectionalVector getLeftDirection() { - return new DirectionalVector( - yaw > -PId2 ? (yaw - PId2) : (yaw - PId2 + PIx2), - 0 - ); - } - - - - /** - * If the current direction is the player face direction, - * this method return the direction of the right of the head. - */ - public DirectionalVector getRightDirection() { - return new DirectionalVector( - yaw < PId2 ? (yaw + PId2) : (yaw + PId2 - PIx2), - 0 - ); - } - - - - - } - - - - - -}