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.block.BlockFire;
023import net.minecraft.block.state.IBlockState;
024import net.minecraft.client.entity.EntityPlayerSP;
025import net.minecraft.entity.Entity;
026import net.minecraft.util.math.*;
027
028import java.util.Optional;
029
030/**
031 * @author Brady
032 * @since 9/25/2018
033 */
034public final class RotationUtils {
035
036    /**
037     * Constant that a degree value is multiplied by to get the equivalent radian value
038     */
039    public static final double DEG_TO_RAD = Math.PI / 180.0;
040
041    /**
042     * Constant that a radian value is multiplied by to get the equivalent degree value
043     */
044    public static final double RAD_TO_DEG = 180.0 / Math.PI;
045
046    /**
047     * Offsets from the root block position to the center of each side.
048     */
049    private static final Vec3d[] BLOCK_SIDE_MULTIPLIERS = new Vec3d[]{
050            new Vec3d(0.5, 0, 0.5), // Down
051            new Vec3d(0.5, 1, 0.5), // Up
052            new Vec3d(0.5, 0.5, 0), // North
053            new Vec3d(0.5, 0.5, 1), // South
054            new Vec3d(0, 0.5, 0.5), // West
055            new Vec3d(1, 0.5, 0.5)  // East
056    };
057
058    private RotationUtils() {}
059
060    /**
061     * Calculates the rotation from BlockPos<sub>dest</sub> to BlockPos<sub>orig</sub>
062     *
063     * @param orig The origin position
064     * @param dest The destination position
065     * @return The rotation from the origin to the destination
066     */
067    public static Rotation calcRotationFromCoords(BlockPos orig, BlockPos dest) {
068        return calcRotationFromVec3d(new Vec3d(orig), new Vec3d(dest));
069    }
070
071    /**
072     * Wraps the target angles to a relative value from the current angles. This is done by
073     * subtracting the current from the target, normalizing it, and then adding the current
074     * angles back to it.
075     *
076     * @param current The current angles
077     * @param target  The target angles
078     * @return The wrapped angles
079     */
080    public static Rotation wrapAnglesToRelative(Rotation current, Rotation target) {
081        if (current.yawIsReallyClose(target)) {
082            return new Rotation(current.getYaw(), target.getPitch());
083        }
084        return target.subtract(current).normalize().add(current);
085    }
086
087    /**
088     * Calculates the rotation from Vec<sub>dest</sub> to Vec<sub>orig</sub> and makes the
089     * return value relative to the specified current rotations.
090     *
091     * @param orig    The origin position
092     * @param dest    The destination position
093     * @param current The current rotations
094     * @return The rotation from the origin to the destination
095     * @see #wrapAnglesToRelative(Rotation, Rotation)
096     */
097    public static Rotation calcRotationFromVec3d(Vec3d orig, Vec3d dest, Rotation current) {
098        return wrapAnglesToRelative(current, calcRotationFromVec3d(orig, dest));
099    }
100
101    /**
102     * Calculates the rotation from Vec<sub>dest</sub> to Vec<sub>orig</sub>
103     *
104     * @param orig The origin position
105     * @param dest The destination position
106     * @return The rotation from the origin to the destination
107     */
108    private static Rotation calcRotationFromVec3d(Vec3d orig, Vec3d dest) {
109        double[] delta = {orig.x - dest.x, orig.y - dest.y, orig.z - dest.z};
110        double yaw = MathHelper.atan2(delta[0], -delta[2]);
111        double dist = Math.sqrt(delta[0] * delta[0] + delta[2] * delta[2]);
112        double pitch = MathHelper.atan2(delta[1], dist);
113        return new Rotation(
114                (float) (yaw * RAD_TO_DEG),
115                (float) (pitch * RAD_TO_DEG)
116        );
117    }
118
119    /**
120     * Calculates the look vector for the specified yaw/pitch rotations.
121     *
122     * @param rotation The input rotation
123     * @return Look vector for the rotation
124     */
125    public static Vec3d calcVec3dFromRotation(Rotation rotation) {
126        float f = MathHelper.cos(-rotation.getYaw() * (float) DEG_TO_RAD - (float) Math.PI);
127        float f1 = MathHelper.sin(-rotation.getYaw() * (float) DEG_TO_RAD - (float) Math.PI);
128        float f2 = -MathHelper.cos(-rotation.getPitch() * (float) DEG_TO_RAD);
129        float f3 = MathHelper.sin(-rotation.getPitch() * (float) DEG_TO_RAD);
130        return new Vec3d((double) (f1 * f2), (double) f3, (double) (f * f2));
131    }
132
133    /**
134     * @param ctx Context for the viewing entity
135     * @param pos The target block position
136     * @return The optional rotation
137     * @see #reachable(EntityPlayerSP, BlockPos, double)
138     */
139    public static Optional<Rotation> reachable(IPlayerContext ctx, BlockPos pos) {
140        return reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance());
141    }
142
143    public static Optional<Rotation> reachable(IPlayerContext ctx, BlockPos pos, boolean wouldSneak) {
144        return reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance(), wouldSneak);
145    }
146
147    /**
148     * Determines if the specified entity is able to reach the center of any of the sides
149     * of the specified block. It first checks if the block center is reachable, and if so,
150     * that rotation will be returned. If not, it will return the first center of a given
151     * side that is reachable. The return type will be {@link Optional#empty()} if the entity is
152     * unable to reach any of the sides of the block.
153     *
154     * @param entity             The viewing entity
155     * @param pos                The target block position
156     * @param blockReachDistance The block reach distance of the entity
157     * @return The optional rotation
158     */
159    public static Optional<Rotation> reachable(EntityPlayerSP entity, BlockPos pos, double blockReachDistance) {
160        return reachable(entity, pos, blockReachDistance, false);
161    }
162
163    public static Optional<Rotation> reachable(EntityPlayerSP entity, BlockPos pos, double blockReachDistance, boolean wouldSneak) {
164        IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForPlayer(entity);
165        if (baritone.getPlayerContext().isLookingAt(pos)) {
166            /*
167             * why add 0.0001?
168             * to indicate that we actually have a desired pitch
169             * the way we indicate that the pitch can be whatever and we only care about the yaw
170             * is by setting the desired pitch to the current pitch
171             * setting the desired pitch to the current pitch + 0.0001 means that we do have a desired pitch, it's
172             * just what it currently is
173             *
174             * or if you're a normal person literally all this does it ensure that we don't nudge the pitch to a normal level
175             */
176            Rotation hypothetical = new Rotation(entity.rotationYaw, entity.rotationPitch + 0.0001F);
177            if (wouldSneak) {
178                // the concern here is: what if we're looking at it now, but as soon as we start sneaking we no longer are
179                RayTraceResult result = RayTraceUtils.rayTraceTowards(entity, hypothetical, blockReachDistance, true);
180                if (result != null && result.typeOfHit == RayTraceResult.Type.BLOCK && result.getBlockPos().equals(pos)) {
181                    return Optional.of(hypothetical); // yes, if we sneaked we would still be looking at the block
182                }
183            } else {
184                return Optional.of(hypothetical);
185            }
186        }
187        Optional<Rotation> possibleRotation = reachableCenter(entity, pos, blockReachDistance, wouldSneak);
188        //System.out.println("center: " + possibleRotation);
189        if (possibleRotation.isPresent()) {
190            return possibleRotation;
191        }
192
193        IBlockState state = entity.world.getBlockState(pos);
194        AxisAlignedBB aabb = state.getBoundingBox(entity.world, pos);
195        for (Vec3d sideOffset : BLOCK_SIDE_MULTIPLIERS) {
196            double xDiff = aabb.minX * sideOffset.x + aabb.maxX * (1 - sideOffset.x);
197            double yDiff = aabb.minY * sideOffset.y + aabb.maxY * (1 - sideOffset.y);
198            double zDiff = aabb.minZ * sideOffset.z + aabb.maxZ * (1 - sideOffset.z);
199            possibleRotation = reachableOffset(entity, pos, new Vec3d(pos).add(xDiff, yDiff, zDiff), blockReachDistance, wouldSneak);
200            if (possibleRotation.isPresent()) {
201                return possibleRotation;
202            }
203        }
204        return Optional.empty();
205    }
206
207    /**
208     * Determines if the specified entity is able to reach the specified block with
209     * the given offsetted position. The return type will be {@link Optional#empty()} if
210     * the entity is unable to reach the block with the offset applied.
211     *
212     * @param entity             The viewing entity
213     * @param pos                The target block position
214     * @param offsetPos          The position of the block with the offset applied.
215     * @param blockReachDistance The block reach distance of the entity
216     * @return The optional rotation
217     */
218    public static Optional<Rotation> reachableOffset(Entity entity, BlockPos pos, Vec3d offsetPos, double blockReachDistance, boolean wouldSneak) {
219        Vec3d eyes = wouldSneak ? RayTraceUtils.inferSneakingEyePosition(entity) : entity.getPositionEyes(1.0F);
220        Rotation rotation = calcRotationFromVec3d(eyes, offsetPos, new Rotation(entity.rotationYaw, entity.rotationPitch));
221        RayTraceResult result = RayTraceUtils.rayTraceTowards(entity, rotation, blockReachDistance, wouldSneak);
222        //System.out.println(result);
223        if (result != null && result.typeOfHit == RayTraceResult.Type.BLOCK) {
224            if (result.getBlockPos().equals(pos)) {
225                return Optional.of(rotation);
226            }
227            if (entity.world.getBlockState(pos).getBlock() instanceof BlockFire && result.getBlockPos().equals(pos.down())) {
228                return Optional.of(rotation);
229            }
230        }
231        return Optional.empty();
232    }
233
234    /**
235     * Determines if the specified entity is able to reach the specified block where it is
236     * looking at the direct center of it's hitbox.
237     *
238     * @param entity             The viewing entity
239     * @param pos                The target block position
240     * @param blockReachDistance The block reach distance of the entity
241     * @return The optional rotation
242     */
243    public static Optional<Rotation> reachableCenter(Entity entity, BlockPos pos, double blockReachDistance, boolean wouldSneak) {
244        return reachableOffset(entity, pos, VecUtils.calculateBlockCenter(entity.world, pos), blockReachDistance, wouldSneak);
245    }
246}