diff --git a/src/api/java/baritone/api/utils/BetterBlockPos.java b/src/api/java/baritone/api/utils/BetterBlockPos.java index 99290419e..1050d955b 100644 --- a/src/api/java/baritone/api/utils/BetterBlockPos.java +++ b/src/api/java/baritone/api/utils/BetterBlockPos.java @@ -245,10 +245,10 @@ public final class BetterBlockPos extends BlockPos { @Nonnull public String toString() { return String.format( - "BetterBlockPos{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) + "BetterBlockPos{x=%d,y=%d,z=%d}", + x, + y, + z ); } } diff --git a/src/main/java/baritone/builder/BlockStateCachedData.java b/src/main/java/baritone/builder/BlockStateCachedData.java index e09793e1a..706cdbf03 100644 --- a/src/main/java/baritone/builder/BlockStateCachedData.java +++ b/src/main/java/baritone/builder/BlockStateCachedData.java @@ -28,10 +28,11 @@ import java.util.*; public final class BlockStateCachedData { private static final BlockStateCachedData[] PER_STATE = Main.DATA_PROVIDER.all(); - public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(false, true, true); + public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(false, true, true, Half.EITHER, false); public final boolean canWalkOn; public final boolean isAir; + public final boolean mustSneakWhenPlacingAgainstMe; private final boolean[] presentsTopHalfFaceForPlacement; private final boolean[] presentsBottomHalfFaceForPlacement; private final List options; @@ -40,17 +41,17 @@ public final class BlockStateCachedData { return PER_STATE[state]; } - public BlockStateCachedData(boolean isAir, boolean canPlaceAgainstAtAll, boolean canWalkOn) { - this(isAir, canPlaceAgainstAtAll, canWalkOn, Half.EITHER); - } - - public BlockStateCachedData(boolean isAir, boolean canPlaceAgainstAtAll, boolean canWalkOn, Half half) { + public BlockStateCachedData(boolean isAir, boolean canPlaceAgainstAtAll, boolean canWalkOn, Half half, boolean mustSneakWhenPlacingAgainstMe) { this.isAir = isAir; this.canWalkOn = canWalkOn; + this.mustSneakWhenPlacingAgainstMe = mustSneakWhenPlacingAgainstMe; this.options = Collections.unmodifiableList(calcOptions(canPlaceAgainstAtAll, half)); this.presentsTopHalfFaceForPlacement = new boolean[Face.VALUES.length]; this.presentsBottomHalfFaceForPlacement = new boolean[Face.VALUES.length]; setupFacesPresented(canPlaceAgainstAtAll, half); + if (mustSneakWhenPlacingAgainstMe && half != Half.EITHER) { + throw new IllegalArgumentException(); + } } private void setupFacesPresented(boolean canPlaceAgainstAtAll, Half half) { @@ -86,22 +87,23 @@ public final class BlockStateCachedData { } @Nullable - private Half presentsFace(Face face, Half half) { + private PlaceAgainstData presentsFace(Face face, Half half) { if ((face == Face.UP || face == Face.DOWN) && half != Half.EITHER) { throw new IllegalStateException(); } boolean top = presentsTopHalfFaceForPlacement[face.index] && (half == Half.EITHER || half == Half.TOP); boolean bottom = presentsBottomHalfFaceForPlacement[face.index] && (half == Half.EITHER || half == Half.BOTTOM); + Half intersectedHalf; // the half that both we present, and they accept. not necessarily equal to either. slab-against-block and block-against-slab will both have this as top/bottom, not either. if (top && bottom) { - return Half.EITHER; + intersectedHalf = Half.EITHER; + } else if (top) { + intersectedHalf = Half.TOP; + } else if (bottom) { + intersectedHalf = Half.BOTTOM; + } else { + return null; } - if (top) { - return Half.TOP; - } - if (bottom) { - return Half.BOTTOM; - } - return null; + return PlaceAgainstData.get(intersectedHalf, mustSneakWhenPlacingAgainstMe); } private List calcOptions(boolean canPlaceAgainstAtAll, Half half) { @@ -135,9 +137,9 @@ public final class BlockStateCachedData { @Nullable - public Half canBeDoneAgainstMe(BlockStatePlacementOption placement) { + public PlaceAgainstData canBeDoneAgainstMe(BlockStatePlacementOption placement) { if (Main.fakePlacementForPerformanceTesting) { - return Main.RAND.nextInt(10) < 8 ? Half.EITHER : null; + return Main.RAND.nextInt(10) < 8 ? PlaceAgainstData.EITHER : null; } Face myFace = placement.against.opposite(); diff --git a/src/main/java/baritone/builder/BlockStatePlacementOption.java b/src/main/java/baritone/builder/BlockStatePlacementOption.java index 8ed356922..bc790b9fc 100644 --- a/src/main/java/baritone/builder/BlockStatePlacementOption.java +++ b/src/main/java/baritone/builder/BlockStatePlacementOption.java @@ -17,7 +17,10 @@ package baritone.builder; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -47,7 +50,14 @@ public class BlockStatePlacementOption { validate(against, half, playerMustBeFacing); } - public Optional computeTraceOptions(Half againstHalf, int playerSupportingX, double playerEyeY, int playerSupportingZ, PlayerVantage vantage, double blockReachDistance) { + /** + * This value must be greater than the face projections. + *

+ * Otherwise certain stair placements would not work. This is verified in this class's sanityCheck. + */ + private static final double LOOSE_CENTER_DISTANCE = 0.15; + + public List computeTraceOptions(Half againstHalf, int playerSupportingX, double playerEyeY, int playerSupportingZ, PlayerVantage vantage, double blockReachDistance) { if (againstHalf != half && half != Half.EITHER) { // narrowing is ok (EITHER -> TOP/BOTTOM) but widening isn't (TOP/BOTTOM -> EITHER) throw new IllegalStateException(); } @@ -55,10 +65,10 @@ public class BlockStatePlacementOption { Vec2d center = Vec2d.HALVED_CENTER.plus(playerSupportingX, playerSupportingZ); switch (vantage) { case LOOSE_CENTER: { - acceptableVantages.add(center.plus(0.15, 0)); - acceptableVantages.add(center.plus(-0.15, 0)); - acceptableVantages.add(center.plus(0, 0.15)); - acceptableVantages.add(center.plus(0, -0.15)); + acceptableVantages.add(center.plus(LOOSE_CENTER_DISTANCE, 0)); + acceptableVantages.add(center.plus(-LOOSE_CENTER_DISTANCE, 0)); + acceptableVantages.add(center.plus(0, LOOSE_CENTER_DISTANCE)); + acceptableVantages.add(center.plus(0, -LOOSE_CENTER_DISTANCE)); // no break! } // FALLTHROUGH! case STRICT_CENTER: { @@ -79,35 +89,59 @@ public class BlockStatePlacementOption { // direction from placed block to place-against block = this.against long blockPlacedAt = 0; long placeAgainstPos = against.offset(blockPlacedAt); - double sq = blockReachDistance * blockReachDistance; - return acceptableVantages + return sanityCheckTraces(acceptableVantages .stream() .map(playerEyeXZ -> new Vec3d(playerEyeXZ.x, playerEyeY, playerEyeXZ.z)) .flatMap(eye -> Stream.of(FACE_PROJECTION_CACHE[against.index]) - .filter(hit -> eye.distSq(hit) < sq) + .filter(this::hitOk) + .filter(hit -> eye.distSq(hit) < blockReachDistance * blockReachDistance) + .filter(hit -> directionOk(eye, hit)) .>>map(hit -> () -> Raytracer.runTrace(eye, placeAgainstPos, against.opposite(), hit)) ) .collect(Collectors.toList()) - .parallelStream() // wrap it like this because flatMap forces .sequential() on the interior child stream, defeating the point + // TODO switch back to parallelStream + .stream() // wrap it like this because flatMap forces .sequential() on the interior child stream, defeating the point .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) - .min(Comparator.naturalOrder()); + .sorted() + .collect(Collectors.toList())); + } + + private boolean hitOk(Vec3d hit) { + if (half == Half.EITHER) { + return true; + } else if (hit.y == 0.1) { + return half == Half.BOTTOM; + } else if (hit.y == 0.5) { + return false; + } else if (hit.y == 0.9) { + return half == Half.TOP; + } else { + throw new IllegalStateException(); + } + } + + private boolean directionOk(Vec3d eye, Vec3d hit) { + if (playerMustBeFacing.isPresent()) { + return eye.flatDirectionTo(hit) == playerMustBeFacing.get(); + } + // TODO include the other conditions for stupid blocks like pistons + return true; } private static final Vec3d[][] FACE_PROJECTION_CACHE; static { - Vec2d center = new Vec2d(0.5, 0.5); - double[] diffs = {-0.4, 0, +0.4}; + double[] diffs = {0.1, 0.5, 0.9}; FACE_PROJECTION_CACHE = new Vec3d[Face.NUM_FACES][diffs.length * diffs.length]; for (Face face : Face.VALUES) { int i = 0; for (double dx : diffs) { for (double dz : diffs) { - FACE_PROJECTION_CACHE[face.index][i++] = new Vec3d(project(center.plus(dx, dz).toArr(), face)); + FACE_PROJECTION_CACHE[face.index][i++] = new Vec3d(project(new double[]{dx, dz}, face)); } } } @@ -173,4 +207,58 @@ public class BlockStatePlacementOption { } }); } + + static { + if (Main.DEBUG) { + sanityCheck(); + } + } + + public static void sanityCheck() { + // standing at 1,0,0 + // block to be placed at 0,0,0 + // placing against 0,0,-1 + + // eye is at 1, 1.62, 0 + // north or west + + StringBuilder sanity = new StringBuilder(); + for (PlayerVantage vantage : new PlayerVantage[]{PlayerVantage.STRICT_CENTER, PlayerVantage.LOOSE_CENTER}) { + for (Face playerFacing : new Face[]{Face.NORTH, Face.EAST, Face.WEST}) { + sanity.append(vantage).append(playerFacing); + List traces = BlockStatePlacementOption.get(Face.NORTH, Half.BOTTOM, Optional.of(playerFacing)).computeTraceOptions(Half.BOTTOM, 1, 1.62, 0, vantage, 4); + sanity.append(traces.size()); + sanity.append(" "); + if (!traces.isEmpty()) { + for (double d : new double[]{traces.get(0).playerEye.x, traces.get(0).playerEye.z}) { + double base = d > 1 ? 1.5 : 0.5; + boolean a = d == base - LOOSE_CENTER_DISTANCE; + boolean b = d == base; + boolean c = d == base + LOOSE_CENTER_DISTANCE; + if (!a && !b && !c) { + throw new IllegalStateException("Wrong " + d); + } + sanity.append(a).append(" ").append(b).append(" ").append(c).append(" "); + } + } + sanity.append(traces.stream().mapToDouble(Raytracer.Raytrace::centerDistApprox).distinct().count()); + sanity.append(";"); + } + } + + String res = sanity.toString(); + String should = "STRICT_CENTERNORTH0 0;STRICT_CENTEREAST0 0;STRICT_CENTERWEST3 false true false false true false 1;LOOSE_CENTERNORTH2 true false false false true false 1;LOOSE_CENTEREAST0 0;LOOSE_CENTERWEST13 false true false false true false 2;"; + if (!res.equals(should)) { + System.out.println(res); + System.out.println(should); + throw new IllegalStateException(res); + } + } + + private static List sanityCheckTraces(List traces) { + if (Main.DEBUG && traces.stream().mapToDouble(Raytracer.Raytrace::centerDistApprox).distinct().count() > 2) { + throw new IllegalStateException(); + } + return traces; + } } diff --git a/src/main/java/baritone/builder/Main.java b/src/main/java/baritone/builder/Main.java index 516c59e11..3355f7622 100644 --- a/src/main/java/baritone/builder/Main.java +++ b/src/main/java/baritone/builder/Main.java @@ -19,6 +19,8 @@ package baritone.builder; import baritone.api.utils.BetterBlockPos; import baritone.builder.mc.VanillaBlockStateDataProvider; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; import net.minecraft.block.Block; import net.minecraft.init.Blocks; @@ -85,7 +87,7 @@ public class Main { { System.out.println(BetterBlockPos.fromLong(BetterBlockPos.toLong(150, 150, 150))); } - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 0; i++) { long aaa = System.currentTimeMillis(); int[][][] test = new int[64][64][64]; int based = Block.BLOCK_STATE_IDS.get(Blocks.DIRT.getDefaultState()); @@ -141,14 +143,7 @@ public class Main { Thread.sleep(500); //scaffolding.enable(0); } - { - // stadning at 1,0,0 - // block to be placed at 0,0,0 - // placing against 0,0,-1 - - // eye is at 1, 1.62, 0 - } - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 0; i++) { Stream.of(new Object()) .flatMap(ignored -> IntStream.range(0, 100).boxed()) .parallel() @@ -161,6 +156,89 @@ public class Main { .collect(Collectors.toList()).parallelStream() .forEach(x -> System.out.println(x + "")); } + { + BlockStatePlacementOption.sanityCheck(); + } + { + /*Raytracer.raytraceMode++; + Raytracer.raytraceMode %= 3;*/ + Random rand = new Random(5021); + DoubleArrayList A = new DoubleArrayList(); + DoubleArrayList B = new DoubleArrayList(); + DoubleArrayList C = new DoubleArrayList(); + DoubleArrayList D = new DoubleArrayList(); + DoubleArrayList E = new DoubleArrayList(); + DoubleArrayList F = new DoubleArrayList(); + LongArrayList G = new LongArrayList(); + long a = System.currentTimeMillis(); + for (int trial = 0; trial < 10_000_000; ) { + Vec3d playerEye = new Vec3d(rand.nextDouble() * 5 - 2.5, rand.nextDouble() * 5, rand.nextDouble() * 5 - 2.5); + long eyeBlock = playerEye.getRoundedToZeroPositionUnsafeDontUse(); + if (eyeBlock == 0) { + // origin, unlucky + continue; + } + Face placeToAgainst = Face.VALUES[rand.nextInt(Face.NUM_FACES)]; + Face againstToPlace = placeToAgainst.opposite(); + long placeAgainst = placeToAgainst.offset(0); + if (eyeBlock == placeAgainst) { + continue; + } + double[] hitVec = new double[3]; + for (int i = 0; i < 3; i++) { + switch (placeToAgainst.vec[i]) { + case -1: { + hitVec[i] = 0; + break; + } + case 0: { + hitVec[i] = rand.nextDouble(); + break; + } + case 1: { + hitVec[i] = 1; + break; + } + } + } + Vec3d hit = new Vec3d(hitVec); + Raytracer.runTrace(playerEye, placeAgainst, againstToPlace, hit); + A.add(playerEye.x); + B.add(playerEye.y); + C.add(playerEye.z); + D.add(hit.x); + E.add(hit.y); + F.add(hit.z); + G.add(placeAgainst); + trial++; + } + long b = System.currentTimeMillis(); + System.out.println("Nominal first run with overhead: " + (b - a) + "ms"); + for (int it = 0; it < 20; it++) { + { + Thread.sleep(1000); + System.gc(); + Thread.sleep(1000); + long start = System.currentTimeMillis(); + for (int i = 0; i < 10_000_000; i++) { + Raytracer.rayTraceZoomy(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i), G.getLong(i)); + } + long end = System.currentTimeMillis(); + System.out.println("Branchless took " + (end - start) + "ms"); + } + { + Thread.sleep(1000); + System.gc(); + Thread.sleep(1000); + long start = System.currentTimeMillis(); + for (int i = 0; i < 10_000_000; i++) { + Raytracer.rayTraceZoomyBranchy(A.getDouble(i), B.getDouble(i), C.getDouble(i), D.getDouble(i), E.getDouble(i), F.getDouble(i), G.getLong(i)); + } + long end = System.currentTimeMillis(); + System.out.println("Branchy took " + (end - start) + "ms"); + } + } + } System.exit(0); } } diff --git a/src/main/java/baritone/builder/PlaceAgainstData.java b/src/main/java/baritone/builder/PlaceAgainstData.java new file mode 100644 index 000000000..a603c3b99 --- /dev/null +++ b/src/main/java/baritone/builder/PlaceAgainstData.java @@ -0,0 +1,49 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.builder; + +public enum PlaceAgainstData { + TOP(Half.TOP, false), + BOTTOM(Half.BOTTOM, false), + EITHER(Half.EITHER, false), + SNEAK_TOP(Half.TOP, true), // only if you need to, like, place a top slab against a crafting table + SNEAK_BOTTOM(Half.BOTTOM, true), // only if you need to, like, place a top slab against a crafting table + SNEAK_EITHER(Half.EITHER, true); + + public final Half half; // like if its a slab + public final boolean mustSneak; // like if its a crafting table + + PlaceAgainstData(Half half, boolean mustSneak) { + this.half = half; + this.mustSneak = mustSneak; + } + + public static PlaceAgainstData get(Half half, boolean mustSneak) { + // oh, the things i do, out of fear of the garbage collector + switch (half) { + case TOP: + return mustSneak ? SNEAK_TOP : TOP; + case BOTTOM: + return mustSneak ? SNEAK_BOTTOM : BOTTOM; + case EITHER: + return mustSneak ? SNEAK_EITHER : EITHER; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/baritone/builder/Raytracer.java b/src/main/java/baritone/builder/Raytracer.java index 697ca9660..8d56cebf1 100644 --- a/src/main/java/baritone/builder/Raytracer.java +++ b/src/main/java/baritone/builder/Raytracer.java @@ -22,6 +22,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList; import java.util.Optional; +import java.util.stream.Collectors; /** * Traces rays @@ -29,17 +30,25 @@ import java.util.Optional; public class Raytracer { public static Optional runTrace(Vec3d playerEye, long againstPos, Face againstFace, Vec3d hit) { + // TODO this totally could be cached tho... if (againstFace.offset(againstPos) != 0) { throw new IllegalStateException("sanity check - for now we assume that all placed blocks end up at 0,0,0"); } + if (toLong(playerEye) == 0) { + throw new IllegalStateException("player eye is within the block we want to place, this is maybe possible? idk i suppose you could do this with, like, a torch? still seems weird. idk if this should be allowed"); + } + if (toLong(playerEye) == againstPos) { + throw new IllegalStateException("player eye is within the block we want to place against, this is DEFINITELY impossible"); + } + //System.out.println(BetterBlockPos.fromLong(toLong(playerEye)) + " to " + BetterBlockPos.fromLong(toLong(hit)) + " aka " + playerEye + " to " + hit); if (Main.STRICT_Y && floor(playerEye.y) < 0) { throw new IllegalStateException("im lazy and dont want to fix occupancyCountByY"); } - long hitPos = BetterBlockPos.toLong(floor(hit.x), floor(hit.y), floor(hit.z)); + long hitPos = toLong(hit); if (hitPos != againstPos && hitPos != 0) { throw new IllegalStateException("ambiguous or incorrect hitvec?"); } - LongArrayList trace = rayTrace(playerEye.x, playerEye.y, playerEye.z, hit.x, hit.y, hit.z); + LongArrayList trace = rayTrace(playerEye.x, playerEye.y, playerEye.z, hit.x, hit.y, hit.z, againstPos); if (trace.size() < 2) { throw new IllegalStateException(); } @@ -52,6 +61,10 @@ public class Raytracer { return Optional.of(new Raytrace(playerEye, againstPos, againstFace, hit, trace)); } + private static long toLong(Vec3d hit) { + return BetterBlockPos.toLong(floor(hit.x), floor(hit.y), floor(hit.z)); + } + public static class Raytrace implements Comparable { public final long againstPos; @@ -69,14 +82,20 @@ public class Raytracer { this.playerEye = playerEye; this.hit = hit; if (trace.getLong(trace.size() - 1) != againstPos) { + print(trace); throw new IllegalStateException(); } if (trace.getLong(trace.size() - 2) != 0) { throw new IllegalStateException(); } + for (int i = 0; i < trace.size() - 1; i++) { + if (!adjacent(trace.getLong(i), trace.getLong(i + 1))) { + throw new IllegalStateException(BetterBlockPos.fromLong(trace.getLong(i)) + " to " + BetterBlockPos.fromLong(trace.getLong(i + 1))); + } + } trace.removeLong(trace.size() - 1); // againstPos doesn't ACTUALLY need to be air, so remove it. it was only there for sanity checking and confirming which face we collided with first trace.trim(); - if (trace.getLong(0) != BetterBlockPos.toLong(floor(playerEye.x), floor(playerEye.y), floor(playerEye.z))) { + if (trace.getLong(0) != toLong(playerEye)) { throw new IllegalStateException(); } this.passedThrough = trace.elements(); @@ -91,13 +110,14 @@ public class Raytracer { for (int i = passedThrough.length - 1; i >= 0; i--) { long pos = passedThrough[i]; int y = BetterBlockPos.YfromLong(pos); - if (list.size() == y) { + if (list.size() == y) { // works because we removed the last trace element (against), which could have negative y list.add(0); } if (list.size() != y + 1) { // this works because we go in reverse order throw new IllegalStateException("nonconsecutive"); } if (pos == freebieTop) { + // only here for correctness in spirit, technically not needed for comparison since it will exist in all of them continue; } if (pos == freebieBottom) { @@ -137,24 +157,103 @@ public class Raytracer { } } { // if occupancy counts match, tiebreak with strict center winning over loose center - int cmp = Double.compare(centerDist(), o.centerDist()); + int cmp = Double.compare(centerDistApprox(), o.centerDistApprox()); if (cmp != 0) { return cmp; } } { // if center status matches, finally tiebreak with simple ray length - return Double.compare(hit.distSq(playerEye), o.hit.distSq(o.playerEye)); + return Double.compare(distSq(), o.distSq()); } } - private double centerDist() { + public double centerDistApprox() { + // calculate distance to center of block but intentionally round it off + // the intent is for LOOSE_CENTER to always tie with itself, even though floating point inaccuracy would make it unequal if we did a direct Double.compare double dx = playerEye.x - (floor(playerEye.x) + 0.5d); double dz = playerEye.z - (floor(playerEye.z) + 0.5d); - return dx * dx + dz * dz; + double dist = dx * dx + dz * dz; + dist = Math.round(dist * 1000000); + return dist; + } + + public double distSq() { + return hit.distSq(playerEye); } } - private static LongArrayList rayTrace(double startX, double startY, double startZ, double endX, double endY, double endZ) { + /** + * If I add 10 to all the numbers I raytrace, then subtract them afterwards, then I can just use (int) instead of the nasty BRANCHING floor. + *

+ * No difference raytracing from -2 to -1 as it is to raytrace from 8 to 9. Just add ten! + */ + private static final int POSITIVITY_OFFSET = 10; + + private static final int NUM_STEPS = 10_000; + + public static int raytraceMode = 2; + + private static LongArrayList rayTrace(double rawStartX, double rawStartY, double rawStartZ, double endX, double endY, double endZ, long againstPos) { + LongArrayList slow = raytraceMode == 0 || Main.DEBUG ? rayTraceSlow(rawStartX, rawStartY, rawStartZ, endX, endY, endZ) : null; + LongArrayList fast = raytraceMode == 1 || Main.DEBUG ? rayTraceFast(rawStartX, rawStartY, rawStartZ, endX, endY, endZ) : null; + LongArrayList faster = raytraceMode == 2 || Main.DEBUG ? rayTraceZoomy(rawStartX, rawStartY, rawStartZ, endX, endY, endZ, againstPos) : null; + if (Main.DEBUG) { + if (fast.equals(slow) && fast.equals(faster)) { + } else { + System.out.println(rawStartX + " " + rawStartY + " " + rawStartZ + " " + endX + " " + endY + " " + endZ + " " + againstPos); + print(slow); + print(fast); + print(faster); + throw new IllegalStateException(); + } + } + return fast == null ? slow == null ? faster : slow : fast; + } + + private static void print(LongArrayList trace) { + System.out.println(trace.stream().map(BetterBlockPos::fromLong).collect(Collectors.toList())); + } + + private static LongArrayList rayTraceFast(double rawStartX, double rawStartY, double rawStartZ, double endX, double endY, double endZ) { + if (willFlipSign(rawStartX) || willFlipSign(rawStartY) || willFlipSign(rawStartZ) || willFlipSign(endX) || willFlipSign(endY) || willFlipSign(endZ)) { + throw new IllegalStateException("I suppose this could happen if you set the block reach distance absurdly high? Don't do that."); + } + double diffX = endX - rawStartX; + double diffY = endY - rawStartY; + double diffZ = endZ - rawStartZ; + if (Math.abs(diffX) < 0.1 && Math.abs(diffY) < 0.1 && Math.abs(diffZ) < 0.1) { + // need more checks than before because now the tightest inner do-while does NOT check step against any upper limit at all + // therefore, if diff was zero, it would truly get stuck indefinitely, unlike previously where it would bail out at 10010 + throw new IllegalArgumentException("throwing exception instead of entering infinite inner loop"); + } + double startX = rawStartX + POSITIVITY_OFFSET; + double startY = rawStartY + POSITIVITY_OFFSET; + double startZ = rawStartZ + POSITIVITY_OFFSET; + + int x = Integer.MIN_VALUE; + int y = Integer.MIN_VALUE; + int z = Integer.MIN_VALUE; + LongArrayList voxelsIntersected = new LongArrayList(); + int step = 0; + double mult = 1.0d / NUM_STEPS; + double frac; + while (step < NUM_STEPS) { + do frac = ++step * mult; + while (((x ^ (x = (int) (startX + diffX * frac))) | (y ^ (y = (int) (startY + diffY * frac))) | (z ^ (z = (int) (startZ + diffZ * frac)))) == 0); + voxelsIntersected.add(BetterBlockPos.toLong(x - POSITIVITY_OFFSET, y - POSITIVITY_OFFSET, z - POSITIVITY_OFFSET)); + } + if (step > NUM_STEPS + 1) { + throw new IllegalStateException("No floating point inaccuracies allowed. Or, at least, no more than 2 parts in 10,000 of wiggle room lol. " + step); + } + return voxelsIntersected; + } + + /** + * Here's an alternate implementation of the above that is functionally the same, just simpler (and slower) + *

+ * The inner loop branches seven times instead of one, for example. + */ + private static LongArrayList rayTraceSlow(double startX, double startY, double startZ, double endX, double endY, double endZ) { // i'd love to use strictfp here and on floor, but it could unironically prevent a much needed JIT to native, so I won't, sadly double diffX = endX - startX; double diffY = endY - startY; @@ -164,29 +263,31 @@ public class Raytracer { int prevY = Integer.MIN_VALUE; int prevZ = Integer.MIN_VALUE; LongArrayList ret = new LongArrayList(); - for (int step = 0; step <= 10010; step++) { // not a typo 😈😈😈😈😈😈😈😈😈😈😈 - double frac = step / 10000.0d; // go THROUGH the face by a little bit, poke through into the block - int x = floor(startX + diffX * frac); - int y = floor(startY + diffY * frac); - int z = floor(startZ + diffZ * frac); - if (x == prevX && y == prevY && z == prevZ) { + int ourLimit = NUM_STEPS + 1; + for (int step = 0; step <= ourLimit; step++) { // 1 branch (step <= ourLimit) + double frac = step / (double) NUM_STEPS; // go THROUGH the face by a little bit, poke through into the block + int x = floor(startX + diffX * frac); // 1 branch in floor + int y = floor(startY + diffY * frac); // 1 branch in floor + int z = floor(startZ + diffZ * frac); // 1 branch in floor + if (x == prevX && y == prevY && z == prevZ) { // 3 branches (due to && short circuiting) continue; } prevX = x; prevY = y; prevZ = z; - long toAdd = BetterBlockPos.toLong(x, y, z); - if (!ret.isEmpty()) { - long prev = ret.getLong(ret.size() - 1); - if (!adjacent(toAdd, prev)) { - throw new IllegalStateException(BetterBlockPos.fromLong(prev) + " to " + BetterBlockPos.fromLong(toAdd)); - } - } - ret.add(toAdd); + ret.add(BetterBlockPos.toLong(x, y, z)); } return ret; } + private static boolean willFlipSign(double pos) { + return flippedSign(pos + POSITIVITY_OFFSET); + } + + private static boolean flippedSign(double pos) { + return pos < 1d; + } + private static boolean adjacent(long a, long b) { if (a == b) { throw new IllegalStateException(); @@ -209,4 +310,170 @@ public class Raytracer { return rawCast; } } + + public static LongArrayList rayTraceZoomy(double startX, double startY, double startZ, double endX, double endY, double endZ, long againstPos) { + if (endX < 0 || endX > 1 || endY < 0 || endY > 1 || endZ < 0 || endZ > 1) { + throw new IllegalStateException("won't work"); + } + if (flippedSign(startX += POSITIVITY_OFFSET) | flippedSign(startY += POSITIVITY_OFFSET) | flippedSign(startZ += POSITIVITY_OFFSET) | flippedSign(endX += POSITIVITY_OFFSET) | flippedSign(endY += POSITIVITY_OFFSET) | flippedSign(endZ += POSITIVITY_OFFSET)) { + throw new IllegalStateException("I suppose this could happen if you set the block reach distance absurdly high? Don't do that."); + } + int voxelEndX = BetterBlockPos.XfromLong(againstPos) + POSITIVITY_OFFSET; + int voxelEndY = BetterBlockPos.YfromLong(againstPos) + POSITIVITY_OFFSET; + int voxelEndZ = BetterBlockPos.ZfromLong(againstPos) + POSITIVITY_OFFSET; + + int voxelInX = (int) startX; + int voxelInY = (int) startY; + int voxelInZ = (int) startZ; + if (startX == (double) voxelInX || startY == (double) voxelInY || startZ == (double) voxelInZ) { + throw new IllegalStateException("Integral starting coordinates not supported ever since I removed the -0.0d check"); + } + + LongArrayList voxelsIntersected = new LongArrayList(); + int steps = 64; // default is 200 + while (steps-- >= 0) { + long posAsLong = BetterBlockPos.toLong(voxelInX - POSITIVITY_OFFSET, voxelInY - POSITIVITY_OFFSET, voxelInZ - POSITIVITY_OFFSET); + voxelsIntersected.add(posAsLong); + if (posAsLong == againstPos) { + if (voxelsIntersected.size() == 1 || voxelsIntersected.getLong(voxelsIntersected.size() - 2) != 0) { + voxelsIntersected.add(0); + } + return voxelsIntersected; + } + double nextIntegerX, nextIntegerY, nextIntegerZ; + // potentially more based branchless impl? + nextIntegerX = voxelInX + ((voxelInX - voxelEndX) >>> 31); // if voxelEnd > voxelIn, then voxelIn-voxelEnd will be negative, meaning the sign bit is 1 + nextIntegerY = voxelInY + ((voxelInY - voxelEndY) >>> 31); // if we do an unsigned right shift by 31, that sign bit becomes the LSB + nextIntegerZ = voxelInZ + ((voxelInZ - voxelEndZ) >>> 31); // therefore, this increments nextInteger iff EndX>inX, otherwise it leaves it alone + // remember: don't have to worry about the case when voxelEnd == voxelIn, because nextInteger value wont be used + + double fracIfSkipX = 100; + double fracIfSkipY = 100; + double fracIfSkipZ = 100; + double distanceFromStartToEndX = endX - startX; + double distanceFromStartToEndY = endY - startY; + double distanceFromStartToEndZ = endZ - startZ; + if (voxelEndX != voxelInX) { + fracIfSkipX = (nextIntegerX - startX) / distanceFromStartToEndX; + } + if (voxelEndY != voxelInY) { + fracIfSkipY = (nextIntegerY - startY) / distanceFromStartToEndY; + } + if (voxelEndZ != voxelInZ) { + fracIfSkipZ = (nextIntegerZ - startZ) / distanceFromStartToEndZ; + } + int dx = 0; + int dy = 0; + int dz = 0; + if (fracIfSkipX < fracIfSkipY && fracIfSkipX < fracIfSkipZ) { + // note: voxelEndX == voxelInX is impossible because allowSkip would be set to false in that case, meaning that the elapsed distance would stay at default + dx = (voxelEndX - voxelInX) >>> 31; // TODO should i set this "dx" way up top at the same time as i set nextIntegerX? + startX = nextIntegerX; + startY += distanceFromStartToEndY * fracIfSkipX; + startZ += distanceFromStartToEndZ * fracIfSkipX; + } else if (fracIfSkipY < fracIfSkipZ) { + dy = (voxelEndY - voxelInY) >>> 31; // we want dy=1 when endY < inY + startX += distanceFromStartToEndX * fracIfSkipY; + startY = nextIntegerY; + startZ += distanceFromStartToEndZ * fracIfSkipY; + } else { + dz = (voxelEndZ - voxelInZ) >>> 31; + startX += distanceFromStartToEndX * fracIfSkipZ; + startY += distanceFromStartToEndY * fracIfSkipZ; + startZ = nextIntegerZ; + } + + voxelInX = ((int) startX) - dx; // TODO is it faster to paste this block of 3 lines into each of the 3 if branches? we know 2/3 subtracts will be zero, so it would save two subtracts, but is that worth the longer bytecode? + voxelInY = ((int) startY) - dy; + voxelInZ = ((int) startZ) - dz; + } + print(voxelsIntersected); + throw new IllegalStateException(); + } + + public static LongArrayList rayTraceZoomyBranchy(double startX, double startY, double startZ, double endX, double endY, double endZ, long againstPos) { + if (endX < 0 || endX > 1 || endY < 0 || endY > 1 || endZ < 0 || endZ > 1) { + throw new IllegalStateException("won't work"); + } + if (flippedSign(startX += POSITIVITY_OFFSET) | flippedSign(startY += POSITIVITY_OFFSET) | flippedSign(startZ += POSITIVITY_OFFSET) | flippedSign(endX += POSITIVITY_OFFSET) | flippedSign(endY += POSITIVITY_OFFSET) | flippedSign(endZ += POSITIVITY_OFFSET)) { + throw new IllegalStateException("I suppose this could happen if you set the block reach distance absurdly high? Don't do that."); + } + int voxelEndX = BetterBlockPos.XfromLong(againstPos) + POSITIVITY_OFFSET; + int voxelEndY = BetterBlockPos.YfromLong(againstPos) + POSITIVITY_OFFSET; + int voxelEndZ = BetterBlockPos.ZfromLong(againstPos) + POSITIVITY_OFFSET; + + int voxelInX = (int) startX; + int voxelInY = (int) startY; + int voxelInZ = (int) startZ; + if (startX == (double) voxelInX || startY == (double) voxelInY || startZ == (double) voxelInZ) { + throw new IllegalStateException("Integral starting coordinates not supported ever since I removed the -0.0d check"); + } + + LongArrayList voxelsIntersected = new LongArrayList(); + int steps = 64; // default is 200 + while (steps-- >= 0) { + long posAsLong = BetterBlockPos.toLong(voxelInX - POSITIVITY_OFFSET, voxelInY - POSITIVITY_OFFSET, voxelInZ - POSITIVITY_OFFSET); + voxelsIntersected.add(posAsLong); + if (posAsLong == againstPos) { + if (voxelsIntersected.size() == 1 || voxelsIntersected.getLong(voxelsIntersected.size() - 2) != 0) { + voxelsIntersected.add(0); + } + return voxelsIntersected; + } + double nextIntegerX, nextIntegerY, nextIntegerZ; + // normal impl + nextIntegerX = voxelEndX > voxelInX ? voxelInX + 1 : voxelInX; + nextIntegerY = voxelEndY > voxelInY ? voxelInY + 1 : voxelInY; + nextIntegerZ = voxelEndZ > voxelInZ ? voxelInZ + 1 : voxelInZ; + double fracIfSkipX = 100; + double fracIfSkipY = 100; + double fracIfSkipZ = 100; + double distanceFromStartToEndX = endX - startX; + double distanceFromStartToEndY = endY - startY; + double distanceFromStartToEndZ = endZ - startZ; + if (voxelEndX != voxelInX) { + fracIfSkipX = (nextIntegerX - startX) / distanceFromStartToEndX; + } + if (voxelEndY != voxelInY) { + fracIfSkipY = (nextIntegerY - startY) / distanceFromStartToEndY; + } + if (voxelEndZ != voxelInZ) { + fracIfSkipZ = (nextIntegerZ - startZ) / distanceFromStartToEndZ; + } + int dx = 0; + int dy = 0; + int dz = 0; + if (fracIfSkipX < fracIfSkipY && fracIfSkipX < fracIfSkipZ) { + // note: voxelEndX == voxelInX is impossible because allowSkip would be set to false in that case, meaning that the elapsed distance would stay at default + if (voxelEndX < voxelInX) { + dx = 1; + } + startX = nextIntegerX; + startY += distanceFromStartToEndY * fracIfSkipX; + startZ += distanceFromStartToEndZ * fracIfSkipX; + } else if (fracIfSkipY < fracIfSkipZ) { + if (voxelEndY < voxelInY) { + dy = 1; + } + startX += distanceFromStartToEndX * fracIfSkipY; + startY = nextIntegerY; + startZ += distanceFromStartToEndZ * fracIfSkipY; + } else { + if (voxelEndZ < voxelInZ) { + dz = 1; + } + startX += distanceFromStartToEndX * fracIfSkipZ; + startY += distanceFromStartToEndY * fracIfSkipZ; + startZ = nextIntegerZ; + } + + voxelInX = ((int) startX) - dx; // TODO is it faster to paste this block of 3 lines into each of the 3 if branches? we know 2/3 subtracts will be zero, so it would save two subtracts, but is that worth the longer bytecode? + voxelInY = ((int) startY) - dy; + voxelInZ = ((int) startZ) - dz; + } + print(voxelsIntersected); + throw new IllegalStateException(); + } + + } diff --git a/src/main/java/baritone/builder/Vec3d.java b/src/main/java/baritone/builder/Vec3d.java index aa0a8e3ad..5c6f513c6 100644 --- a/src/main/java/baritone/builder/Vec3d.java +++ b/src/main/java/baritone/builder/Vec3d.java @@ -17,6 +17,8 @@ package baritone.builder; +import baritone.api.utils.BetterBlockPos; + public class Vec3d { public final double x; @@ -36,10 +38,51 @@ public class Vec3d { } } + public long getRoundedToZeroPositionUnsafeDontUse() { + return BetterBlockPos.toLong((int) x, (int) y, (int) z); + } + public double distSq(Vec3d other) { double dx = x - other.x; double dy = y - other.y; double dz = z - other.z; return dx * dx + dy * dy + dz * dz; } + + public Face flatDirectionTo(Vec3d dst) { + return new Vec3d(dst.x - x, dst.y - y, dst.z - z).flatDirection(); + } + + public Face flatDirection() { + if (Math.abs(x) == Math.abs(z)) { + throw new IllegalStateException("ambiguous"); + } + if (Math.abs(x) > Math.abs(z)) { + if (x > 0) { + return Face.EAST; + } else { + return Face.WEST; + } + } else { + if (z > 0) { + return Face.SOUTH; + } else { + return Face.NORTH; + } + } + } + + @Override + public String toString() { + return "Vec3d{" + x + "," + y + "," + z + "}"; + } + + static { + for (Face face : Face.HORIZONTALS) { + Face flat = new Vec3d(face.x, face.y, face.z).flatDirection(); + if (flat != face) { + throw new IllegalStateException(); + } + } + } } diff --git a/src/main/java/baritone/builder/mc/BlockStatePropertiesExtractor.java b/src/main/java/baritone/builder/mc/BlockStatePropertiesExtractor.java index 51f1e6afd..3c6e7b549 100644 --- a/src/main/java/baritone/builder/mc/BlockStatePropertiesExtractor.java +++ b/src/main/java/baritone/builder/mc/BlockStatePropertiesExtractor.java @@ -18,6 +18,7 @@ package baritone.builder.mc; import baritone.builder.BlockStateCachedData; +import baritone.builder.Half; import net.minecraft.block.Block; import net.minecraft.block.BlockAir; import net.minecraft.block.state.IBlockState; @@ -34,9 +35,9 @@ public class BlockStatePropertiesExtractor { Block block = state.getBlock(); if (block instanceof BlockAir) { - return new BlockStateCachedData(true, false, false); + return new BlockStateCachedData(true, false, false, Half.EITHER, false); } boolean normal = block == Blocks.COBBLESTONE || block == Blocks.DIRT; - return new BlockStateCachedData(false, normal, normal); + return new BlockStateCachedData(false, normal, normal, Half.EITHER, false); } }