|
1 | 1 | package ch.njol.skript.util; |
2 | 2 |
|
3 | | -import ch.njol.skript.bukkitutil.WorldUtils; |
4 | | -import ch.njol.util.Math2; |
5 | 3 | import org.bukkit.Location; |
| 4 | +import org.bukkit.World; |
6 | 5 | import org.bukkit.block.Block; |
7 | | -import org.bukkit.util.BlockIterator; |
8 | 6 | import org.bukkit.util.Vector; |
9 | | -import org.jetbrains.annotations.Nullable; |
| 7 | +import org.jetbrains.annotations.Contract; |
| 8 | +import org.jetbrains.annotations.NotNull; |
| 9 | +import org.joml.Vector3d; |
10 | 10 |
|
11 | | -import ch.njol.util.NullableChecker; |
12 | | -import ch.njol.util.coll.iterator.StoppableIterator; |
| 11 | +import java.util.Iterator; |
| 12 | +import java.util.NoSuchElementException; |
13 | 13 |
|
14 | | -public class BlockLineIterator extends StoppableIterator<Block> { |
| 14 | +/** |
| 15 | + * Iterates through blocks in a straight line from a start to end location (inclusive). |
| 16 | + * <p> |
| 17 | + * Given start and end locations are always cloned but may not be block-centered. |
| 18 | + * Iterates through all blocks the line passes through in order from start to end location. |
| 19 | + */ |
| 20 | +public class BlockLineIterator implements Iterator<Block> { |
| 21 | + |
| 22 | + private final Vector3d current; |
| 23 | + private final Vector3d end; |
| 24 | + private final Vector3d centeredEnd; |
| 25 | + final Vector3d step; // package private for tests |
| 26 | + private final World world; |
| 27 | + private boolean finished; |
| 28 | + |
| 29 | + /** |
| 30 | + * @param start start location |
| 31 | + * @param end end location |
| 32 | + */ |
| 33 | + public BlockLineIterator(@NotNull Location start, @NotNull Location end) { |
| 34 | + this.current = start.toVector().toVector3d(); |
| 35 | + this.world = start.getWorld(); |
| 36 | + this.end = end.toVector().toVector3d(); |
| 37 | + this.centeredEnd = centered(this.end); |
| 38 | + this.step = this.end.sub(current, new Vector3d()).normalize(); |
| 39 | + } |
| 40 | + |
| 41 | + /** |
| 42 | + * @param start first block |
| 43 | + * @param end last block |
| 44 | + */ |
| 45 | + public BlockLineIterator(@NotNull Block start, @NotNull Block end) { |
| 46 | + this(start.getLocation().toCenterLocation(), end.getLocation().toCenterLocation()); |
| 47 | + } |
15 | 48 |
|
16 | 49 | /** |
17 | | - * @param start |
18 | | - * @param end |
19 | | - * @throws IllegalStateException randomly (Bukkit bug) |
| 50 | + * @param start start location |
| 51 | + * @param direction direction to travel in |
| 52 | + * @param distance maximum distance to travel |
20 | 53 | */ |
21 | | - public BlockLineIterator(Block start, Block end) throws IllegalStateException { |
22 | | - super(new BlockIterator(start.getWorld(), start.getLocation().toVector(), |
23 | | - end.equals(start) ? new Vector(1, 0, 0) : end.getLocation().subtract(start.getLocation()).toVector(), // should prevent an error if start = end |
24 | | - 0, 0 |
25 | | - ), |
26 | | - new NullableChecker<Block>() { |
27 | | - private final double overshotSq = Math.pow(start.getLocation().distance(end.getLocation()) + 2, 2); |
28 | | - |
29 | | - @Override |
30 | | - public boolean check(@Nullable Block block) { |
31 | | - assert block != null; |
32 | | - if (block.getLocation().distanceSquared(start.getLocation()) > overshotSq) |
33 | | - throw new IllegalStateException("BlockLineIterator missed the end block!"); |
34 | | - return block.equals(end); |
35 | | - } |
36 | | - }, true); |
| 54 | + public BlockLineIterator(Location start, @NotNull Vector direction, double distance) { |
| 55 | + this(start, start.clone().add(direction.clone().normalize().multiply(distance))); |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * @param start first block |
| 60 | + * @param direction direction to travel in |
| 61 | + * @param distance maximum distance to travel |
| 62 | + */ |
| 63 | + public BlockLineIterator(@NotNull Block start, Vector direction, double distance) { |
| 64 | + this(start.getLocation().toCenterLocation(), direction, distance); |
| 65 | + } |
| 66 | + |
| 67 | + @Override |
| 68 | + public boolean hasNext() { |
| 69 | + return !finished; |
| 70 | + } |
| 71 | + |
| 72 | + @Override |
| 73 | + public Block next() { |
| 74 | + if (!hasNext()) throw new NoSuchElementException("Reached the final block destination"); |
| 75 | + // sanity check (is the current->end vector pointing away from step) |
| 76 | + if (end.sub(current, new Vector3d()).dot(step) < 0) throw new NoSuchElementException("Overshot the final block!"); |
| 77 | + // get block and check end |
| 78 | + Vector3d center = centered(current); |
| 79 | + Block block = getBlock(center, world); |
| 80 | + if (center.equals(centeredEnd)) finished = true; |
| 81 | + // calculate next position |
| 82 | + double t = stepsToNextFace(current, step, center) + Math.ulp(1); |
| 83 | + current.fma(t, step); |
| 84 | + return block; |
37 | 85 | } |
38 | 86 |
|
39 | 87 | /** |
40 | | - * @param start |
41 | | - * @param direction |
42 | | - * @param distance |
43 | | - * @throws IllegalStateException randomly (Bukkit bug) |
| 88 | + * Calculates the number of steps to the next closest block face this ray, defined by start and step, will encounter. |
| 89 | + * Block faces are determined by the center vector, which is interpreted as the center of the block. |
| 90 | + * @param start the current location of the ray to check. |
| 91 | + * @param step the direction of the ray. |
| 92 | + * @param center the center location of the block the ray is currently within. |
| 93 | + * @return a scalar floating point number representing the number of times step must be added to start in order |
| 94 | + * to arrive at the closest block face. |
44 | 95 | */ |
45 | | - public BlockLineIterator(Location start, Vector direction, double distance) throws IllegalStateException { |
46 | | - super(new BlockIterator(start.getWorld(), start.toVector(), direction, 0, 0), new NullableChecker<Block>() { |
47 | | - private final double distSq = distance * distance; |
48 | | - |
49 | | - @Override |
50 | | - public boolean check(final @Nullable Block b) { |
51 | | - return b != null && b.getLocation().add(0.5, 0.5, 0.5).distanceSquared(start) >= distSq; |
52 | | - } |
53 | | - }, false); |
| 96 | + static double stepsToNextFace(Vector3d start, @NotNull Vector3d step, Vector3d center) { |
| 97 | + Vector3d neededSteps = new Vector3d(Math.signum(step.x), Math.signum(step.y), Math.signum(step.z)) |
| 98 | + .mulAdd(0.5, center) |
| 99 | + .sub(start) |
| 100 | + .div(step, new Vector3d()); // need to make new vector due to JOML method signature issue |
| 101 | + // get min component, ignoring NaN |
| 102 | + if (Double.isNaN(neededSteps.x)) |
| 103 | + neededSteps.x = Double.POSITIVE_INFINITY; |
| 104 | + if (Double.isNaN(neededSteps.y)) |
| 105 | + neededSteps.y = Double.POSITIVE_INFINITY; |
| 106 | + if (Double.isNaN(neededSteps.z)) |
| 107 | + neededSteps.z = Double.POSITIVE_INFINITY; |
| 108 | + return neededSteps.get(neededSteps.minComponent()); |
54 | 109 | } |
55 | 110 |
|
56 | 111 | /** |
57 | | - * @param start |
58 | | - * @param direction |
59 | | - * @param distance |
60 | | - * @throws IllegalStateException randomly (Bukkit bug) |
| 112 | + * Creates vector at the center of a block at the coordinates provided |
| 113 | + * by {@code vector}. |
| 114 | + * |
| 115 | + * @param vector point |
| 116 | + * @return coordinates at the center of a block at given point |
61 | 117 | */ |
62 | | - public BlockLineIterator(Block start, Vector direction, double distance) throws IllegalStateException { |
63 | | - this(start.getLocation().add(0.5, 0.5, 0.5), direction, distance); |
| 118 | + @Contract("_ -> new") |
| 119 | + private static Vector3d centered(@NotNull Vector3d vector) { |
| 120 | + return vector.floor(new Vector3d()).add(0.5, 0.5, 0.5); |
64 | 121 | } |
65 | 122 |
|
66 | 123 | /** |
67 | | - * Makes the vector fit within the world parameters. |
68 | | - * |
69 | | - * @param location The original starting location. |
70 | | - * @param direction The direction of the vector that will be based on the location. |
71 | | - * @return The newly modified Vector if needed. |
| 124 | + * @param vector the xyz coordinates of the block to get. |
| 125 | + * @param world the world which the block should be obtained from |
| 126 | + * @return the block at the given xyz coords in the given world. |
72 | 127 | */ |
73 | | - private static Vector fitInWorld(Location location, Vector direction) { |
74 | | - int lowest = WorldUtils.getWorldMinHeight(location.getWorld()); |
75 | | - int highest = location.getWorld().getMaxHeight(); |
76 | | - Vector vector = location.toVector(); |
77 | | - int y = location.getBlockY(); |
78 | | - if (y >= lowest && y <= highest) |
79 | | - return vector; |
80 | | - double newY = Math2.fit(lowest, location.getY(), highest); |
81 | | - return new Vector(location.getX(), newY, location.getZ()); |
| 128 | + private static @NotNull Block getBlock(@NotNull Vector3d vector, @NotNull World world) { |
| 129 | + return Vector.fromJOML(vector).toLocation(world).getBlock(); |
82 | 130 | } |
83 | 131 |
|
84 | 132 | } |
0 commit comments