diff options
-rw-r--r-- | src/main/java/org/bukkit/Location.java | 95 | ||||
-rw-r--r-- | src/test/java/org/bukkit/LocationTest.java | 196 |
2 files changed, 277 insertions, 14 deletions
diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java index e3f362fd..0f74f6d4 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -167,45 +167,79 @@ public class Location implements Cloneable { } /** - * Sets the yaw of this location - * - * @param yaw New yaw + * Sets the yaw of this location, measured in degrees. + * <ul> + * <li>A yaw of 0 or 360 represents the positive z direction. + * <li>A yaw of 180 represents the negative z direction. + * <li>A yaw of 90 represents the negative x direction. + * <li>A yaw of 270 represents the positive x direction. + * </ul> + * Increasing yaw values are the equivalent of turning to your + * right-facing, increasing the scale of the next respective axis, and + * decreasing the scale of the previous axis. + * + * @param yaw new rotation's yaw */ public void setYaw(float yaw) { this.yaw = yaw; } /** - * Gets the yaw of this location + * Gets the yaw of this location, measured in degrees. + * <ul> + * <li>A yaw of 0 or 360 represents the positive z direction. + * <li>A yaw of 180 represents the negative z direction. + * <li>A yaw of 90 represents the negative x direction. + * <li>A yaw of 270 represents the positive x direction. + * </ul> + * Increasing yaw values are the equivalent of turning to your + * right-facing, increasing the scale of the next respective axis, and + * decreasing the scale of the previous axis. * - * @return Yaw + * @return the rotation's yaw */ public float getYaw() { return yaw; } /** - * Sets the pitch of this location + * Sets the pitch of this location, measured in degrees. + * <ul> + * <li>A pitch of 0 represents level forward facing. + * <li>A pitch of 90 represents downward facing, or negative y + * direction. + * <li>A pitch of -90 represents upward facing, or positive y direction. + * <ul> + * Increasing pitch values the equivalent of looking down. * - * @param pitch New pitch + * @param pitch new incline's pitch */ public void setPitch(float pitch) { this.pitch = pitch; } /** - * Gets the pitch of this location + * Sets the pitch of this location, measured in degrees. + * <ul> + * <li>A pitch of 0 represents level forward facing. + * <li>A pitch of 90 represents downward facing, or negative y + * direction. + * <li>A pitch of -90 represents upward facing, or positive y direction. + * <ul> + * Increasing pitch values the equivalent of looking down. * - * @return Pitch + * @return the incline's pitch */ public float getPitch() { return pitch; } /** - * Gets a Vector pointing in the direction that this Location is facing + * Gets a unit-vector pointing in the direction that this Location is + * facing. * - * @return Vector + * @return a vector pointing the direction of this location's {@link + * #getPitch() pitch} and {@link #getYaw() yaw} */ public Vector getDirection() { Vector vector = new Vector(); @@ -215,15 +249,48 @@ public class Location implements Cloneable { vector.setY(-Math.sin(Math.toRadians(rotY))); - double h = Math.cos(Math.toRadians(rotY)); + double xz = Math.cos(Math.toRadians(rotY)); - vector.setX(-h * Math.sin(Math.toRadians(rotX))); - vector.setZ(h * Math.cos(Math.toRadians(rotX))); + vector.setX(-xz * Math.sin(Math.toRadians(rotX))); + vector.setZ(xz * Math.cos(Math.toRadians(rotX))); return vector; } /** + * Sets the {@link #getYaw() yaw} and {@link #getPitch() pitch} to point + * in the direction of the vector. + */ + public Location setDirection(Vector vector) { + /* + * Sin = Opp / Hyp + * Cos = Adj / Hyp + * Tan = Opp / Adj + * + * x = -Opp + * z = Adj + */ + final double _2PI = 2 * Math.PI; + final double x = vector.getX(); + final double z = vector.getZ(); + + if (x == 0 && z == 0) { + pitch = vector.getY() > 0 ? -90 : 90; + return this; + } + + double theta = Math.atan2(-x, z); + yaw = (float) Math.toDegrees((theta + _2PI) % _2PI); + + double x2 = NumberConversions.square(x); + double z2 = NumberConversions.square(z); + double xz = Math.sqrt(x2 + z2); + pitch = (float) Math.toDegrees(Math.atan(-vector.getY() / xz)); + + return this; + } + + /** * Adds the location by another. * * @see Vector diff --git a/src/test/java/org/bukkit/LocationTest.java b/src/test/java/org/bukkit/LocationTest.java new file mode 100644 index 00000000..fa247763 --- /dev/null +++ b/src/test/java/org/bukkit/LocationTest.java @@ -0,0 +1,196 @@ +package org.bukkit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Random; + +import org.bukkit.util.Vector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.collect.ImmutableList; + +@RunWith(Parameterized.class) +public class LocationTest { + private static final double δ = 1.0 / 1000000; + /** + * <pre> + * a² + b² = c², a = b + * => 2∙(a²) = 2∙(b²) = c², c = 1 + * => 2∙(a²) = 1 + * => a² = 1/2 + * => a = √(1/2) ∎ + * </pre> + */ + private static final double HALF_UNIT = Math.sqrt(1 / 2f); + /** + * <pre> + * a² + b² = c², c = √(1/2) + * => a² + b² = √(1/2)², a = b + * => 2∙(a²) = 2∙(b²) = 1/2 + * => a² = 1/4 + * => a = √(1/4) ∎ + * </pre> + */ + private static final double HALF_HALF_UNIT = Math.sqrt(1 / 4f); + + @Parameters(name= "{index}: {0}") + public static List<Object[]> data() { + Random RANDOM = new Random(1l); // Test is deterministic + int r = 0; + return ImmutableList.<Object[]>of( + new Object[] { "X", + 1, 0, 0, + 270, 0 + }, + new Object[] { "-X", + -1, 0, 0, + 90, 0 + }, + new Object[] { "Z", + 0, 0, 1, + 0, 0 + }, + new Object[] { "-Z", + 0, 0, -1, + 180, 0 + }, + new Object[] { "Y", + 0, 1, 0, + 0, -90 // Zero is here as a "default" value + }, + new Object[] { "-Y", + 0, -1, 0, + 0, 90 // Zero is here as a "default" value + }, + new Object[] { "X Z", + HALF_UNIT, 0, HALF_UNIT, + (270 + 360) / 2, 0 + }, + new Object[] { "X -Z", + HALF_UNIT, 0, -HALF_UNIT, + (270 + 180) / 2, 0 + }, + new Object[] { "-X -Z", + -HALF_UNIT, 0, -HALF_UNIT, + (90 + 180) / 2, 0 + }, + new Object[] { "-X Z", + -HALF_UNIT, 0, HALF_UNIT, + (90 + 0) / 2, 0 + }, + new Object[] { "X Y Z", + HALF_HALF_UNIT, HALF_UNIT, HALF_HALF_UNIT, + (270 + 360) / 2, -45 + }, + new Object[] { "-X -Y -Z", + -HALF_HALF_UNIT, -HALF_UNIT, -HALF_HALF_UNIT, + (90 + 180) / 2, 45 + }, + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++) + ); + } + + private static Object[] getRandom(Random random, int index) { + final double YAW_FACTOR = 360; + final double YAW_OFFSET = 0; + final double PITCH_FACTOR = 180; + final double PITCH_OFFSET = -90; + final double CARTESIAN_FACTOR = 256; + final double CARTESIAN_OFFSET = -128; + + Vector vector; + Location location; + if (random.nextBoolean()) { + float pitch = (float) (random.nextDouble() * PITCH_FACTOR + PITCH_OFFSET); + float yaw = (float) (random.nextDouble() * YAW_FACTOR + YAW_OFFSET); + + location = getEmptyLocation(); + location.setPitch(pitch); + location.setYaw(yaw); + + vector = location.getDirection(); + } else { + double x = random.nextDouble() * CARTESIAN_FACTOR + CARTESIAN_OFFSET; + double y = random.nextDouble() * CARTESIAN_FACTOR + CARTESIAN_OFFSET; + double z = random.nextDouble() * CARTESIAN_FACTOR + CARTESIAN_OFFSET; + + location = getEmptyLocation(); + vector = new Vector(x, y, z).normalize(); + + location.setDirection(vector); + } + + return new Object[] { "R" + index, + vector.getX(), vector.getY(), vector.getZ(), + location.getYaw(), location.getPitch() + }; + } + + @Parameter(0) + public String nane; + @Parameter(1) + public double x; + @Parameter(2) + public double y; + @Parameter(3) + public double z; + @Parameter(4) + public float yaw; + @Parameter(5) + public float pitch; + + @Test + public void testExpectedPitchYaw() { + Location location = getEmptyLocation().setDirection(getVector()); + + assertThat((double) location.getYaw(), is(closeTo(yaw, δ))); + assertThat((double) location.getPitch(), is(closeTo(pitch, δ))); + } + + @Test + public void testExpectedXYZ() { + Vector vector = getLocation().getDirection(); + + assertThat(vector.getX(), is(closeTo(x, δ))); + assertThat(vector.getY(), is(closeTo(y, δ))); + assertThat(vector.getZ(), is(closeTo(z, δ))); + } + + private Vector getVector() { + return new Vector(x, y, z); + } + + private static Location getEmptyLocation() { + return new Location(null, 0, 0, 0); + } + + private Location getLocation() { + Location location = getEmptyLocation(); + location.setYaw(yaw); + location.setPitch(pitch); + return location; + } +} |