Skip to content

Instantly share code, notes, and snippets.

@jarcode-foss
Created June 26, 2014 04:35
Show Gist options
  • Save jarcode-foss/e59936d475266eae402a to your computer and use it in GitHub Desktop.
Save jarcode-foss/e59936d475266eae402a to your computer and use it in GitHub Desktop.
Determine if given location is directed at an entity
public static boolean isLookingAt(Entity entity, Location eye) {
double yaw = eye.getYaw() > 0 ? eye.getYaw() : 360 - Math.abs(eye.getYaw()); // remove negative degrees
yaw += 90; // rotate +90 degrees
if (yaw > 360)
yaw -= 360;
yaw = (yaw * Math.PI) / 180;
double pitch = ((eye.getPitch() + 90) * Math.PI) / 180;
AxisAlignedBB box = ((CraftLivingEntity) entity).getHandle().boundingBox;
// two dimensional coordinates that correspond to the corners of the bounding box on the horizontal plane (x, z)
double[][] hCoords = new double[][] {
{box.a, box.c}, {box.d, box.c}, {box.a, box.f}, {box.d, box.f}
};
Double[] thetas = new Double[4];
// convert to theta in polar coordinates
for (int t = 0; t < 4; t++)
thetas[t] = toPolar(hCoords[t][0] - eye.getX(), hCoords[t][1] - eye.getZ());
// Calculate the required range for this entity
Range<Double, Double> yawRange = getLargestArcRange(thetas);
// Doing this for pitch is a bit more difficult, we need to use all corners of the bounding box in 3D space
double[][] corners = new double[][] {
{box.a, box.b, box.c}, {box.d, box.b, box.c}, {box.a, box.b, box.f}, {box.d, box.b, box.f},
{box.a, box.e, box.c}, {box.d, box.e, box.c}, {box.a, box.e, box.f}, {box.d, box.e, box.f},
};
Double[] phis = new Double[8];
for (int t = 0; t < 8; t++) {
double xo = corners[t][0] - eye.getX();
double yo = corners[t][1] - eye.getY();
double zo = corners[t][2] - eye.getZ();
// convert to phi angle in spherical coordinates
phis[t] = Math.acos(yo / Math.sqrt((xo * xo) + (yo * yo) + (zo * zo)));
}
Range<Double, Double> pitchRange = getLargestArcRange(phis);
return inside(yaw, yawRange.getLowest(), yawRange.getHighest()) && inside(pitch, pitchRange.getLowest(), pitchRange.getHighest());
}
// Sorts combinations of angles and returns the combination of angles with the largest distance between them
public static Range<Double, Double> getLargestArcRange(Double[] angles) {
Set<Set<Double>> combinations = getCombinationsFor(Arrays.asList(angles), 2);
Range<Double, Double> largestRange = null;
Double largest = null;
for (Set<Double> combo : combinations) {
Double[] array = combo.toArray(new Double[2]);
double arc = distance(array[0], array[1]);
if (largest == null || arc > largest) {
largest = arc;
largestRange = new Range<>(array[0], array[1]);
}
}
if (largestRange != null && largestRange.getLowest() > largestRange.getHighest())
largestRange = new Range<>(largestRange.getHighest(), largestRange.getLowest());
return largestRange;
}
// Found from stackoverflow somewhere, because I'm lazy. I rewrote it to use generics, it's useful for sorting.
public static <T> Set<Set<T>> getCombinationsFor(List<T> group, int k) {
Set<Set<T>> allCombos = new HashSet<>();
if (k == 0) {
allCombos.add(new HashSet<T>());
return allCombos;
}
if (k > group.size())
return allCombos;
List<T> groupWithoutX = new ArrayList<>(group);
T x = groupWithoutX.remove(groupWithoutX.size()-1);
Set<Set<T>> combosWithoutX = getCombinationsFor(groupWithoutX, k);
Set<Set<T>> combosWithX = getCombinationsFor(groupWithoutX, k-1);
for (Set<T> combo : combosWithX)
combo.add(x);
allCombos.addAll(combosWithoutX);
allCombos.addAll(combosWithX);
return allCombos;
}
// distance between two angles, in radians
private static double distance(double r1, double r2) {
double d = Math.abs(r2 - r1);
if (d <= Math.PI)
return d;
else return 2 * Math.PI - d;
}
// [0, 2pi)
private static double toPolar(double x, double y) {
double theta = Math.atan2(y, x);
if (theta < 0)
return 2 * Math.PI + theta;
else return theta;
}
private static boolean inside(double theta, double small, double large) {
// If the range includes 0 rad
if (large - small > Math.PI)
return theta > large || theta < small;
else return theta < large && theta > small;
}
private static class Range<L extends Number, H extends Number> {
L lowest;
H highest;
Range(L lowest, H highest) {
this.lowest = lowest;
this.highest = highest;
}
public L getLowest() {
return lowest;
}
public H getHighest() {
return highest;
}
public void setKey(L lowest) {
this.lowest = lowest;
}
public void setValue(H highest) {
this.highest = highest;
}
}
@jarcode-foss
Copy link
Author

This code does not work if the 'eye' location is inside the entity's bounding box. Add this at line 10 if you want to catch overlap cases:

if (box.a(Vec3D.a(eye.getX(), eye.getY(), eye.getZ())))
return true;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment