001/*
002 * This file is part of Baritone.
003 *
004 * Baritone is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU Lesser General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * Baritone is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License
015 * along with Baritone.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package baritone.api.utils;
019
020import baritone.api.BaritoneAPI;
021import baritone.api.IBaritone;
022import net.minecraft.client.player.LocalPlayer;
023import net.minecraft.core.BlockPos;
024import net.minecraft.core.Direction;
025import net.minecraft.util.Mth;
026import net.minecraft.world.entity.Entity;
027import net.minecraft.world.level.block.BaseFireBlock;
028import net.minecraft.world.level.block.state.BlockState;
029import net.minecraft.world.phys.BlockHitResult;
030import net.minecraft.world.phys.HitResult;
031import net.minecraft.world.phys.Vec3;
032import net.minecraft.world.phys.shapes.Shapes;
033import net.minecraft.world.phys.shapes.VoxelShape;
034
035import java.util.Optional;
036
037/**
038 * @author Brady
039 * @since 9/25/2018
040 */
041public final class RotationUtils {
042
043    /**
044     * Constant that a degree value is multiplied by to get the equivalent radian value
045     */
046    public static final double DEG_TO_RAD = Math.PI / 180.0;
047    public static final float DEG_TO_RAD_F = (float) DEG_TO_RAD;
048
049    /**
050     * Constant that a radian value is multiplied by to get the equivalent degree value
051     */
052    public static final double RAD_TO_DEG = 180.0 / Math.PI;
053    public static final float RAD_TO_DEG_F = (float) RAD_TO_DEG;
054
055    /**
056     * Offsets from the root block position to the center of each side.
057     */
058    private static final Vec3[] BLOCK_SIDE_MULTIPLIERS = new Vec3[]{
059            new Vec3(0.5, 0, 0.5), // Down
060            new Vec3(0.5, 1, 0.5), // Up
061            new Vec3(0.5, 0.5, 0), // North
062            new Vec3(0.5, 0.5, 1), // South
063            new Vec3(0, 0.5, 0.5), // West
064            new Vec3(1, 0.5, 0.5)  // East
065    };
066
067    private RotationUtils() {}
068
069    /**
070     * Calculates the rotation from BlockPos<sub>dest</sub> to BlockPos<sub>orig</sub>
071     *
072     * @param orig The origin position
073     * @param dest The destination position
074     * @return The rotation from the origin to the destination
075     */
076    public static Rotation calcRotationFromCoords(BlockPos orig, BlockPos dest) {
077        return calcRotationFromVec3d(new Vec3(orig.getX(), orig.getY(), orig.getZ()), new Vec3(dest.getX(), dest.getY(), dest.getZ()));
078    }
079
080    /**
081     * Wraps the target angles to a relative value from the current angles. This is done by
082     * subtracting the current from the target, normalizing it, and then adding the current
083     * angles back to it.
084     *
085     * @param current The current angles
086     * @param target  The target angles
087     * @return The wrapped angles
088     */
089    public static Rotation wrapAnglesToRelative(Rotation current, Rotation target) {
090        if (current.yawIsReallyClose(target)) {
091            return new Rotation(current.getYaw(), target.getPitch());
092        }
093        return target.subtract(current).normalize().add(current);
094    }
095
096    /**
097     * Calculates the rotation from Vec<sub>dest</sub> to Vec<sub>orig</sub> and makes the
098     * return value relative to the specified current rotations.
099     *
100     * @param orig    The origin position
101     * @param dest    The destination position
102     * @param current The current rotations
103     * @return The rotation from the origin to the destination
104     * @see #wrapAnglesToRelative(Rotation, Rotation)
105     */
106    public static Rotation calcRotationFromVec3d(Vec3 orig, Vec3 dest, Rotation current) {
107        return wrapAnglesToRelative(current, calcRotationFromVec3d(orig, dest));
108    }
109
110    /**
111     * Calculates the rotation from Vec<sub>dest</sub> to Vec<sub>orig</sub>
112     *
113     * @param orig The origin position
114     * @param dest The destination position
115     * @return The rotation from the origin to the destination
116     */
117    private static Rotation calcRotationFromVec3d(Vec3 orig, Vec3 dest) {
118        double[] delta = {orig.x - dest.x, orig.y - dest.y, orig.z - dest.z};
119        double yaw = Mth.atan2(delta[0], -delta[2]);
120        double dist = Math.sqrt(delta[0] * delta[0] + delta[2] * delta[2]);
121        double pitch = Mth.atan2(delta[1], dist);
122        return new Rotation(
123                (float) (yaw * RAD_TO_DEG),
124                (float) (pitch * RAD_TO_DEG)
125        );
126    }
127
128    /**
129     * Calculates the look vector for the specified yaw/pitch rotations.
130     *
131     * @param rotation The input rotation
132     * @return Look vector for the rotation
133     */
134    public static Vec3 calcLookDirectionFromRotation(Rotation rotation) {
135        float flatZ = Mth.cos((-rotation.getYaw() * DEG_TO_RAD_F) - (float) Math.PI);
136        float flatX = Mth.sin((-rotation.getYaw() * DEG_TO_RAD_F) - (float) Math.PI);
137        float pitchBase = -Mth.cos(-rotation.getPitch() * DEG_TO_RAD_F);
138        float pitchHeight = Mth.sin(-rotation.getPitch() * DEG_TO_RAD_F);
139        return new Vec3(flatX * pitchBase, pitchHeight, flatZ * pitchBase);
140    }
141
142    @Deprecated
143    public static Vec3 calcVec3dFromRotation(Rotation rotation) {
144        return calcLookDirectionFromRotation(rotation);
145    }
146
147    /**
148     * @param ctx Context for the viewing entity
149     * @param pos The target block position
150     * @return The optional rotation
151     * @see #reachable(IPlayerContext, BlockPos, double)
152     */
153    public static Optional<Rotation> reachable(IPlayerContext ctx, BlockPos pos) {
154        return reachable(ctx, pos, false);
155    }
156
157    public static Optional<Rotation> reachable(IPlayerContext ctx, BlockPos pos, boolean wouldSneak) {
158        return reachable(ctx, pos, ctx.playerController().getBlockReachDistance(), wouldSneak);
159    }
160
161    /**
162     * Determines if the specified entity is able to reach the center of any of the sides
163     * of the specified block. It first checks if the block center is reachable, and if so,
164     * that rotation will be returned. If not, it will return the first center of a given
165     * side that is reachable. The return type will be {@link Optional#empty()} if the entity is
166     * unable to reach any of the sides of the block.
167     *
168     * @param ctx                Context for the viewing entity
169     * @param pos                The target block position
170     * @param blockReachDistance The block reach distance of the entity
171     * @return The optional rotation
172     */
173    public static Optional<Rotation> reachable(IPlayerContext ctx, BlockPos pos, double blockReachDistance) {
174        return reachable(ctx, pos, blockReachDistance, false);
175    }
176
177    public static Optional<Rotation> reachable(IPlayerContext ctx, BlockPos pos, double blockReachDistance, boolean wouldSneak) {
178        if (BaritoneAPI.getSettings().remainWithExistingLookDirection.value && ctx.isLookingAt(pos)) {
179            /*
180             * why add 0.0001?
181             * to indicate that we actually have a desired pitch
182             * the way we indicate that the pitch can be whatever and we only care about the yaw
183             * is by setting the desired pitch to the current pitch
184             * setting the desired pitch to the current pitch + 0.0001 means that we do have a desired pitch, it's
185             * just what it currently is
186             *
187             * or if you're a normal person literally all this does it ensure that we don't nudge the pitch to a normal level
188             */
189            Rotation hypothetical = ctx.playerRotations().add(new Rotation(0, 0.0001F));
190            if (wouldSneak) {
191                // the concern here is: what if we're looking at it now, but as soon as we start sneaking we no longer are
192                HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), hypothetical, blockReachDistance, true);
193                if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(pos)) {
194                    return Optional.of(hypothetical); // yes, if we sneaked we would still be looking at the block
195                }
196            } else {
197                return Optional.of(hypothetical);
198            }
199        }
200        Optional<Rotation> possibleRotation = reachableCenter(ctx, pos, blockReachDistance, wouldSneak);
201        //System.out.println("center: " + possibleRotation);
202        if (possibleRotation.isPresent()) {
203            return possibleRotation;
204        }
205
206        BlockState state = ctx.world().getBlockState(pos);
207        VoxelShape shape = state.getShape(ctx.world(), pos);
208        if (shape.isEmpty()) {
209            shape = Shapes.block();
210        }
211        for (Vec3 sideOffset : BLOCK_SIDE_MULTIPLIERS) {
212            double xDiff = shape.min(Direction.Axis.X) * sideOffset.x + shape.max(Direction.Axis.X) * (1 - sideOffset.x);
213            double yDiff = shape.min(Direction.Axis.Y) * sideOffset.y + shape.max(Direction.Axis.Y) * (1 - sideOffset.y);
214            double zDiff = shape.min(Direction.Axis.Z) * sideOffset.z + shape.max(Direction.Axis.Z) * (1 - sideOffset.z);
215            possibleRotation = reachableOffset(ctx, pos, new Vec3(pos.getX(), pos.getY(), pos.getZ()).add(xDiff, yDiff, zDiff), blockReachDistance, wouldSneak);
216            if (possibleRotation.isPresent()) {
217                return possibleRotation;
218            }
219        }
220        return Optional.empty();
221    }
222
223    /**
224     * Determines if the specified entity is able to reach the specified block with
225     * the given offsetted position. The return type will be {@link Optional#empty()} if
226     * the entity is unable to reach the block with the offset applied.
227     *
228     * @param ctx                Context for the viewing entity
229     * @param pos                The target block position
230     * @param offsetPos          The position of the block with the offset applied.
231     * @param blockReachDistance The block reach distance of the entity
232     * @return The optional rotation
233     */
234    public static Optional<Rotation> reachableOffset(IPlayerContext ctx, BlockPos pos, Vec3 offsetPos, double blockReachDistance, boolean wouldSneak) {
235        Vec3 eyes = wouldSneak ? RayTraceUtils.inferSneakingEyePosition(ctx.player()) : ctx.player().getEyePosition(1.0F);
236        Rotation rotation = calcRotationFromVec3d(eyes, offsetPos, ctx.playerRotations());
237        Rotation actualRotation = BaritoneAPI.getProvider().getBaritoneForPlayer(ctx.player()).getLookBehavior().getAimProcessor().peekRotation(rotation);
238        HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRotation, blockReachDistance, wouldSneak);
239        //System.out.println(result);
240        if (result != null && result.getType() == HitResult.Type.BLOCK) {
241            if (((BlockHitResult) result).getBlockPos().equals(pos)) {
242                return Optional.of(rotation);
243            }
244            if (ctx.world().getBlockState(pos).getBlock() instanceof BaseFireBlock && ((BlockHitResult) result).getBlockPos().equals(pos.below())) {
245                return Optional.of(rotation);
246            }
247        }
248        return Optional.empty();
249    }
250
251    /**
252     * Determines if the specified entity is able to reach the specified block where it is
253     * looking at the direct center of it's hitbox.
254     *
255     * @param ctx                Context for the viewing entity
256     * @param pos                The target block position
257     * @param blockReachDistance The block reach distance of the entity
258     * @return The optional rotation
259     */
260    public static Optional<Rotation> reachableCenter(IPlayerContext ctx, BlockPos pos, double blockReachDistance, boolean wouldSneak) {
261        return reachableOffset(ctx, pos, VecUtils.calculateBlockCenter(ctx.world(), pos), blockReachDistance, wouldSneak);
262    }
263
264    @Deprecated
265    public static Optional<Rotation> reachable(LocalPlayer entity, BlockPos pos, double blockReachDistance) {
266        return reachable(entity, pos, blockReachDistance, false);
267    }
268
269    @Deprecated
270    public static Optional<Rotation> reachable(LocalPlayer entity, BlockPos pos, double blockReachDistance, boolean wouldSneak) {
271        IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForPlayer(entity);
272        IPlayerContext ctx = baritone.getPlayerContext();
273        return reachable(ctx, pos, blockReachDistance, wouldSneak);
274    }
275
276    @Deprecated
277    public static Optional<Rotation> reachableOffset(Entity entity, BlockPos pos, Vec3 offsetPos, double blockReachDistance, boolean wouldSneak) {
278        Vec3 eyes = wouldSneak ? RayTraceUtils.inferSneakingEyePosition(entity) : entity.getEyePosition(1.0F);
279        Rotation rotation = calcRotationFromVec3d(eyes, offsetPos, new Rotation(entity.getYRot(), entity.getXRot()));
280        HitResult result = RayTraceUtils.rayTraceTowards(entity, rotation, blockReachDistance, wouldSneak);
281        //System.out.println(result);
282        if (result != null && result.getType() == HitResult.Type.BLOCK) {
283            if (((BlockHitResult) result).getBlockPos().equals(pos)) {
284                return Optional.of(rotation);
285            }
286            if (entity.level().getBlockState(pos).getBlock() instanceof BaseFireBlock && ((BlockHitResult) result).getBlockPos().equals(pos.below())) {
287                return Optional.of(rotation);
288            }
289        }
290        return Optional.empty();
291    }
292
293    @Deprecated
294    public static Optional<Rotation> reachableCenter(Entity entity, BlockPos pos, double blockReachDistance, boolean wouldSneak) {
295        return reachableOffset(entity, pos, VecUtils.calculateBlockCenter(entity.level(), pos), blockReachDistance, wouldSneak);
296    }
297}