In celebration of Pi Day, I decided to implement the Monte Carlo method to approximate the value of π, but my algorithm doesn’t seem to be working.
I've tried running with different parameters, but I always get approx 3.66
I've tried debugging but I can't figure it out.
public class ApproximatePi {
private int iterations; // how many points to test
private double r; // width of the square / radius of circle (quarter circle)
private int inCount = 0; // number of points that are inside the circle
private int outCount = 0; // number of points outside of the circle
private Random getNum = new Random(System.currentTimeMillis());
ApproximatePi(int iterations, double r) {
this.iterations = iterations;
this.r = r;
// getNum = new Random(System.currentTimeMillis());
}
public double getApproximation() {
for (int i = 0; i < iterations; i++) {
double x = (r) * getNum.nextDouble();
double y = (r) * getNum.nextDouble();
if (inside(x, y)) {
inCount++;
} else
outCount++;
}
double answer = (double) inCount / (double) outCount;
return answer;
}
private boolean inside(double x, double y) {
// if the hypotenuse is greater than the radius, the point is outside the circle
if (getHypot(x, y) >= r) {
return false;
} else
return true;
}
private double getHypot(double x, double y) {
double s1 = Math.pow(x, 2);
double s2 = Math.pow(y, 2);
return Math.sqrt(s1 + s2);
}
}
So, lets assume that radius is 1, so in this case what actually you're doing:
Generate bunch of x,y coordinates within square with coordinates (0,0) - (1,1)
Then you test which of them are within circle with center at (0,0)
By counting in/out counters you're getting how many points within circle's segment and how many outside
inCount / (inCount+outCount) represents ratio between in points to total surface
r² is total surface
Thus, you can get approximate area of 1/4th of circle via formula inCount / (inCount+outCount) * r² == pi * r² / 4
Now, you can say that 4 * inCount / (inCount+outCount) == pi
Related
I'm trying to generate a point within a radius and I'm getting incorrect values. Someone mind taking a look and telling me what I'm doing wrong for the longitude? This was a formulaic approach posted on a different question...
public static Location generateLocationWithinRadius(Location myCurrentLocation) {
return getLocationInLatLngRad(1000, myCurrentLocation);
}
protected static Location getLocationInLatLngRad(double radiusInMeters, Location currentLocation) {
double x0 = currentLocation.getLatitude();
double y0 = currentLocation.getLongitude();
Random random = new Random();
// Convert radius from meters to degrees
double radiusInDegrees = radiusInMeters / 111000f;
double u = random.nextDouble();
double v = random.nextDouble();
double w = radiusInDegrees * Math.sqrt(u);
double t = 2 * Math.PI * v;
double x = w * Math.cos(t);
double y = w * Math.sin(t);
double new_x = x / Math.cos(y0);
double new_y = y / Math.cos(x0);
double foundLatitude;
double foundLongitude;
boolean shouldAddOrSubtractLat = random.nextBoolean();
boolean shouldAddOrSubtractLon = random.nextBoolean();
if (shouldAddOrSubtractLat) {
foundLatitude = new_x + x0;
} else {
foundLatitude = x0 - new_x;
}
if (shouldAddOrSubtractLon) {
foundLongitude = new_y + y0;
} else {
foundLongitude = y0 - new_y;
}
Location copy = new Location(currentLocation);
copy.setLatitude(foundLatitude);
copy.setLongitude(foundLongitude);
return copy;
}
I should also say that for some reason the valid points yield a uniform line of coordinates when looking at them.
I think the latitude is processing correctly whereas the longitude is not.
Your code seems to be more or less based on an idea
which is presented at gis.stackexchange.com
and discussed some more there in this discussion
and in this discussion.
If we take a closer look at it based on those discussions then maybe it makes more sense.
To easily limit the values to a circle it uses the approach of randomizing a direction and a distance. First we get two random double values between 0.0 ... 1.0:
double u = random.nextDouble();
double v = random.nextDouble();
As the radius is given in meters and the calculations require degrees, it's converted:
double radiusInDegrees = radiusInMeters / 111000f;
The degrees vs. meters ratio of the equator is used here. (Wikipedia suggests 111320 m.)
To have a more uniform distribution of the random points the distance is compensated with a square root:
w = r * sqrt(u)
Otherwise there would be a statistical bias in the amount of points near the center vs. far from the center. The square root of 1 is 1 and 0 of course 0, so
multiplying the root of the random double by the intended max. radius always gives a value between 0 and the radius.
Then the other random double is multiplied by 2 * pi because there are 2 * pi radians in a full circle:
t = 2 * Pi * v
We now have an angle somewhere between 0 ... 2 * pi i.e. 0 ... 360 degrees.
Then the random x and y coordinate deltas are calculated with basic trigonometry using the random distance and random angle:
x = w * cos(t)
y = w * sin(t)
The [x,y] then points some random distance w away from the original coordinates towards the direction t.
Then the varying distance between longitude lines is compensated with trigonometry (y0 being the center's y coordinate):
x' = x / cos(y0)
Above y0 needs to be converted to radians if the cos() expects the angle as radians. In Java it does.
It's then suggested that these delta values are added to the original coordinates. The cos and sin are negative for half of the full circle's angles so just adding is fine. Some of the random points will be to the west from Greenwich and and south from the equator. There's no need to randomize
should an addition or subtraction be done.
So the random point would be at (x'+x0, y+y0).
I don't know why your code has:
double new_y = y / Math.cos(x0);
And like said we can ignore shouldAddOrSubtractLat and shouldAddOrSubtractLon.
In my mind x refers to something going from left to right or from west to east. That's how the longitude values grow even though the longitude lines go from south to north. So let's use x as longitude and y as latitude.
So what's left then? Something like:
protected static Location getLocationInLatLngRad(double radiusInMeters, Location currentLocation) {
double x0 = currentLocation.getLongitude();
double y0 = currentLocation.getLatitude();
Random random = new Random();
// Convert radius from meters to degrees.
double radiusInDegrees = radiusInMeters / 111320f;
// Get a random distance and a random angle.
double u = random.nextDouble();
double v = random.nextDouble();
double w = radiusInDegrees * Math.sqrt(u);
double t = 2 * Math.PI * v;
// Get the x and y delta values.
double x = w * Math.cos(t);
double y = w * Math.sin(t);
// Compensate the x value.
double new_x = x / Math.cos(Math.toRadians(y0));
double foundLatitude;
double foundLongitude;
foundLatitude = y0 + y;
foundLongitude = x0 + new_x;
Location copy = new Location(currentLocation);
copy.setLatitude(foundLatitude);
copy.setLongitude(foundLongitude);
return copy;
}
It is hard for me to provide you with a pure Android solution as I never used those API. However I am sure you could easily adapt this solution to generate a random point within a given radius from an existing point.
The problem is solved in a two dimensions space however it is easy to extend to support altitude as well.
Please have a look at the code below. It provides you with a LocationGeneratoras well as my own Location implementation and an unit test proving that it works.
My solution is based on solving the circle equation (x-a)^2 + (y-b)^2 = r^2
package my.test.pkg;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertTrue;
public class LocationGeneratorTest {
private class Location {
double longitude;
double latitude;
public Location(double longitude, double latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
}
private class LocationGenerator {
private final Random random = new Random();
Location generateLocationWithinRadius(Location currentLocation, double radius) {
double a = currentLocation.longitude;
double b = currentLocation.latitude;
double r = radius;
// x must be in (a-r, a + r) range
double xMin = a - r;
double xMax = a + r;
double xRange = xMax - xMin;
// get a random x within the range
double x = xMin + random.nextDouble() * xRange;
// circle equation is (y-b)^2 + (x-a)^2 = r^2
// based on the above work out the range for y
double yDelta = Math.sqrt(Math.pow(r, 2) - Math.pow((x - a), 2));
double yMax = b + yDelta;
double yMin = b - yDelta;
double yRange = yMax - yMin;
// Get a random y within its range
double y = yMin + random.nextDouble() * yRange;
// And finally return the location
return new Location(x, y);
}
}
#Test
public void shoulRandomlyGeneratePointWithinRadius () throws Exception {
LocationGenerator locationGenerator = new LocationGenerator();
Location currentLocation = new Location(20., 10.);
double radius = 5.;
for (int i=0; i < 1000000; i++) {
Location randomLocation = locationGenerator.generateLocationWithinRadius(currentLocation, radius);
try {
assertTrue(Math.pow(randomLocation.latitude - currentLocation.latitude, 2) + Math.pow(randomLocation.longitude - currentLocation.longitude, 2) < Math.pow(radius, 2));
} catch (Throwable e) {
System.out.println("i= " + i + ", x=" + randomLocation.longitude + ", y=" + randomLocation.latitude);
throw new Exception(e);
}
}
}
}
NOTE:
This is just a generic solution to obtain a random point inside a circle with the center in (a, b) and a radius of r that can be used to solve your problem and not a straight solution that you can use as such. You most likely will need to adapt it to your use case.
I believe this is a natural solution.
Regards
Kotlin version of Markus Kauppinen answer
fun Location.getRandomLocation(radius: Double): Location {
val x0: Double = longitude
val y0: Double = latitude
// Convert radius from meters to degrees.
// Convert radius from meters to degrees.
val radiusInDegrees: Double = radius / 111320f
// Get a random distance and a random angle.
// Get a random distance and a random angle.
val u = Random.nextDouble()
val v = Random.nextDouble()
val w = radiusInDegrees * sqrt(u)
val t = 2 * Math.PI * v
// Get the x and y delta values.
// Get the x and y delta values.
val x = w * cos(t)
val y = w * sin(t)
// Compensate the x value.
// Compensate the x value.
val newX = x / cos(Math.toRadians(y0))
val foundLatitude: Double
val foundLongitude: Double
foundLatitude = y0 + y
foundLongitude = x0 + newX
val copy = Location(this)
copy.latitude = foundLatitude
copy.longitude = foundLongitude
return copy
}
Longitude and Latitude uses ellipsoidal coordinates so for big radius (hundred meters) the error using this method would become sinificant. A possible trick is to convert to Cartesian coordinates, do the radius randomization and then transform back again to ellipsoidal coordinates for the long-lat. I have tested this up to a couple of kilometers with great success using this java library from ibm. Longer than that might work, but eventually the radius would fall off as the earth shows its spherical nature.
I have a program to draw 20 circles w/ random rad x and y. After, I need to test which circles are overlapping and if they are, set them cyan, if not set them black. heres what I have so far, the problem, is it always sets it to cyan overlapping or not.
public class AppletP5 extends JApplet{
MyCircle[] circle = new MyCircle[20];
public AppletP5(){
for(int i = 0; i<20; i++){
int x0= (int) (Math.random()*500);
int y0= (int) (Math.random()*500);
int rad0= (int) (30 + Math.random()*70);
circle[i] = new MyCircle(x0,y0,rad0);
}
}
public void paint(Graphics g){
for(int i = 0; i<20; i++){
if(circle[i].isOverlapping(circle) == true){
g.setColor(Color.CYAN);
g.drawOval(circle[i].x,circle[i].y,circle[i].rad*2,circle[i].rad*2);
} else if(circle[i].isOverlapping(circle) == false){
g.setColor(Color.BLACK);
g.drawOval(circle[i].x,circle[i].y,circle[i].rad*2,circle[i].rad*2);
}
}
}
}
public class MyCircle {
protected int x, y, rad;
public MyCircle(int x, int y, int rad){
this.x = x;
this.y = y;
this.rad = rad;
}
public boolean isOverlapping(MyCircle[] circles){
for(MyCircle c : circles){
if(Math.pow(c.rad - rad , 2) >= Math.sqrt(Math.pow(x - c.x, 2) + Math.pow(y - c.y , 2))){
return true;
}
}
return false;
}
}
You need to exclude the current Circle from the comparison, since a circle trivially overlaps itself.
You could go with an easy check as long as you just have one instance of each Circle:
for (MyCirlce c : circles) {
if (c != this && ...)
In addition you are checking if the difference of radii between two circles squared by two is greater than the distance of the two centres? Shouldn't you check for the sum of the radii, eg:
r1 + r2 <= distance(c1, c2)
isOverLapping is incorrect implemented.
Two circles intersect, if the distance between their centers is smaller than the sum of their radii. So:
int radSum = c.rad + rad;
int radDif = c.rad - rad;
int distX = c.x - x + radDif;
int distY = c.y - y + radDif;
if(radSum * radSum < distX * distX + distY * distY)
return true;
Apart from that you'll have to ensure you don't compare a circle with itself. And finally: Math.pow is rather costly, so replace it with the simpler version, if you only want the square of a number.
public class Pond {
public static void allcreationco(){
for (int i = 0; i < 100; i++){
int radius = 100;
int x = (int) (Math.random() * 2 * radius - radius);
int ylim = (int) Math.sqrt(radius * radius - x * x);
int y = (int) (Math.random() * 2 * ylim - ylim);
Fish.xfishc.add((int) x);
Fish.yfishc.add((int) y);
}
allcreationdir();
}
public static void allcreationdir(){
for (int i = 0; i < Fish.xfishc.size(); i++){
double radius = Math.random()*1;
double angle = Math.random()*2*Math.PI;
double x = Math.cos(angle)*radius + 0;
if (x > 0){
Fish.xfishcb1.add(true);
}
else {
Fish.xfishcb1.add(false);
}
}
for (int i = 0; i < Fish.yfishc.size(); i++){
double radius = Math.random()*1;
double angle = Math.random()*2*Math.PI;
double x = Math.cos(angle)*radius + 0;
if (x > 0){
Fish.yfishcb1.add(true);
}
else {
Fish.yfishcb1.add(false);
}
}
Hi, my objective is to create a simulation (no visual drawing needed, just something to easily print info about) of a circular pond with fish randomly swimming in it. The code above is a way of initiating 100 hypothetical fish into Arraylists in the form of x and y coordinates based on a hypothetical circle with a radius of 100 (there's gotta be a better way to do this). I would like to have each of the 100 fish be able to swim in random directions and change to new random directions every time they reach the end of the pond. Maybe I'd like them to reproduce after a certain time, or include another fish that moves in straight lines and can eat another fish.
public class Salmon extends Fish {
public static int salmonre = 0;
public static void salmonmove(){
for (int i = 0; i < xfishc.size(); i++){
if (xfishcb1.get(i) == true){
int d = xfishc.get(i);
int e = d + 1;
xfishc.set(i , e);
}
else{
int d = xfishc.get(i);
int e = d - 1;
xfishc.set(i , e);
}
}
for (int i = 0; i < yfishc.size(); i++){
if (yfishcb1.get(i) == true){
int d = yfishc.get(i);
int e = d + 1;
yfishc.set(i , e);
}
else{
int d = yfishc.get(i);
int e = d - 1;
yfishc.set(i , e);
}
}
salmonre++;
}
}
I also used Boolean arraylists to randomly determine what directions the fish are supposed move in. Please be gentle with me in your rhetoric because I'm well aware that my approaches are ridiculous. I know it's best to use trigonometry when simulating objects and their behaviors in a circle, but for some reason, I'm not able to wrap my head around this when looking on the internet (I keep finding things more complicated that involve visual illustrations). Could you "please" give me comprehensive answers with ideas? I'm frustrated.
I wasn't entirely able to figure out how you wanted your Fish class to work based on your code, but some tips:
In Object Oriented programming, you do not want to have a class Fish that has static methods for updating two lists containing X and Y coordinates for all the fish.
Instead, you want an object of class Fish to represent everything about a single fish. You can then have a list of Fish objects.
A pair of booleans is really too coarse for directions. Use a double instead, one for each fish (stored in the Fish instance).
To implement the direction changing behavior, just check whether the next move would move the fish out of the water, and if so, pick a different direction.
Here's a simple, self contained example of the above for two Fish. They start out together and in the same direction, but diverge when they hit the edge and swim in different, random directions:
class Fish {
private double x, y;
private double angle, speed;
public Fish() {
x = y = angle = 0;
speed = 5;
}
void move() {
// If we swim at this angle, this is where we'll end up
double newX = x + Math.cos(angle) * speed;
double newY = y + Math.sin(angle) * speed;
if (isInPond(newX, newY)) {
// That's still in the pond, go there
x = newX;
y = newY;
} else {
// That's outside the pond, change direction
angle = Math.random() * 2 * Math.PI;
}
}
public String toString() {
return String.format(
"Position: %.0f,%.0f. Angle: %.0f degrees.",
x, y, angle * 180/Math.PI);
}
// Check whether some coordinates are within a circular pond with radius 100
static boolean isInPond(double x, double y) {
return Math.sqrt(x*x+y*y) < 100;
}
public static void main(String[] args) throws Exception {
Fish nemo = new Fish();
Fish marlin = new Fish();
while(true) {
nemo.move();
marlin.move();
System.out.println("Nemo: " + nemo);
System.out.println("Marlin: " + marlin);
Thread.sleep(500);
}
}
}
So I am working on a blackjack game, I have wrote a render process which will render a card going out of the cards stack and sliding to the place where it shows all dealer's cards.
My method works fine, except one problem which I will elaborate:
Whenever Y coordinate reaches the target Y coordinate first, the sprite will only move on X-asis because it cant move Y anymore, instead of making a straight angle to the point.
So what it will do is move up diagonally and then instantly go to the right (in my case)
GIF:
(source: gyazo.com)
MP4 (choose mp4 in the (..) menu http://gyazo.com/bec6daadcb46bedc4777a3e4c5ff8c77)
As you can see, it does what I just said.
What did I do wrong? how can I make it motion in a straight angle to the target without going diagonal up and than turn right away?
My process code:
// If the target is on the right side of the start point
if (startPoint.getX() < endPoint.getX()) {
if (current.getX() < endPoint.getX()) {
current.x += moveSpeed;
if (current.getX() > endPoint.getX()) {
current.x = (int) endPoint.getX();
}
}
else {
xDone = true;
}
}
else {
if (current.getX() > endPoint.getX()) {
current.x -= moveSpeed;
if (current.getX() < endPoint.getX()) {
current.x = (int) endPoint.getX();
}
}
else {
xDone = true;
}
}
// Vise-versa
if (startPoint.getY() < endPoint.getY()) {
if (current.getY() < endPoint.getY()) {
current.y += moveSpeed;
if (current.getY() > endPoint.getY()) {
current.y = (int) endPoint.getY();
}
}
else {
yDone = true;
}
}
else {
if (current.getY() > endPoint.getY()) {
current.y -= moveSpeed;
if (current.getY() < endPoint.getY()) {
current.y = (int) endPoint.getY();
}
}
else {
yDone = true;
}
}
// Callback, dw about it
CardContainer.getCardSprite(CardContainer.SPECIAL, 0).drawSprite((int) current.getX(), (int) current.getY());
// Alert finished, in poisiuton
if (xDone && yDone) {
ch.submitCard(card);
}
current = current position
startPoint = the start point
endPoint = the end point
Thanks!
EDited code:
private void applyMovement(double alpha) {
double dx = endPoint.getX() - startPoint.getX();
double dy = endPoint.getY() - startPoint.getY();
this.current.setLocation(startPoint.getX() + alpha * dx, startPoint.getY() + alpha * dy);
}
public void process() {
double alpha = (double) stepsDone / distance;
applyMovement(alpha);
stepsDone++;
// Callback, dw about it
CardContainer.getCardSprite(CardContainer.SPECIAL, 0).drawSprite((int) current.getX(), (int) current.getY());
// Alert finished, in poisiuton
if (stepsDone >= distance) {
ch.submitCard(card);
}
}
Distance calculation:
this.distance = (int) start.distance(end);
Used Point2D distance method:
public double distance(Point2D pt) {
double px = pt.getX() - this.getX();
double py = pt.getY() - this.getY();
return Math.sqrt(px * px + py * py);
}
I would recommend to not use any form of "slope" in such a computation. You will run into problems when the difference in x-direction approaches zero, because then the slope will tend towards infinity.
Assuming that your points are Point2D.Double (or something similar - you should include this kind of information in your questions!), you can compute the movement as follows:
private Point2D.Double initial = ... // The initial position
private Point2D.Double current = ... // The current position
private Point2D.Double target = ... // The target position
void applyMovment(double alpha) {
double dx = target.getX() - initial.getX();
double dy = target.getY() - initial.getY();
current.x = initial.getX() + alpha * dx;
current.y = initial.getY() + alpha * dy;
}
The applyMovment method sketched here can be called with a double value between 0.0 and 1.0, where 0.0 corresponds to the initial position and 1.0 corresponds to the target position. This is just a Linear Interpolation.
So for example, when you have some sort of loop for the animation, you can use the method as follows:
int numberOfSteps = 10;
for (int i=0; i<=numberOfSteps; i++)
{
double alpha = (double)i / numberOfSteps;
applyMovement(alpha);
repaint();
}
This works for any arrangement of the start- and end points, without any sign- or direction issues. It just interpolates between the two positions.
Your calculation needs to be based upon moving the currentY and currentX along a specific line, not a specific set of intervals (moveSpeed). The formula for graphing points on a line is:
y = mx + b
Where x and y are the varying points, m is equal to the slope of the line, and b is what's called the y-intercept.
Your slope is calculated by the formula:
double slope = ((double) endPoint.getY() - startPoint.getY()) / ((double) endPoint.getX() - startPoint.getX());
And the Y intercept can be calculated by just plugging in a bunch of known values once you have them:
double yIntercept = (double) endPoint.getY() - (slope * endPoint.getX())
Then, just loop through the count of the difference in X:
for (int xVal = startPoint.getX(); xVal < endPoint.getX(); xVal++){
currentX = xVal;
currentY = (slope * xVal) + yIntercept;
}
And you should be good.
Warning: this is all off of the top of my head, I don't know if it'll compile.
Alright, so I am using Bukkit API (Minecraft), which should not be too much of a problem in this as it is used minimally. So a Location contains world, x, y, z, yaw and pitch. This may come in handy, but I doubt it.
My problem is that I go to shoot using the Shot class (below), and there appears to be a +-5 difference in either one when approximately 3 blocks away, and the HitBox is instantiated about 5 blocks away (this could be the issue (move/rotate methods)). I have tried working this out on paper, and have used half a notepad doing so, but I am not yet able to figure out a solution. What I need is someone who understands trigonometry and java well, so they can help out.
Other information that may be of use:
+z is 0 degrees yaw, -x is 90 degrees, -z is 180, and +x is 270.
The variables seem to be incorrect sporadically, and under certain circumstances they work correctly and constitute a hit.
The Location (from) parameter in the Shot constructor is of the player's location in the world, therefore from is not (0, 0, 0).
ShotData should not affect any values, as wind speed and wind direction in my case are 0 (if there is a problem with the math involving this though, feel free to let me know, haha)
Pitch appears to be fine, although +y is -90, and -y is 90 (weird right?)
So my question is... Where is the problem, and how do I fix it? I am sorry that it is such a general question and that it is a very common one, but this is one of those times when it is truly necessary. I tried to remove all unnecessary code, but you can remove more if needed. Also, if you want to see anything else that may be referenced in here, I can get that for you.
Shot.java:
private final Location from;
private ShotData data;
public Shot(Location from, ShotData data) {
this.from = from;
this.data = data;
}
// TODO - Checking for obstacles
public List<Hit> shoot(List<HitBox> hitBoxes) {
List<Hit> hits = new ArrayList<Hit>();
for (HitBox hitBox : hitBoxes) {
hitBox.update();
float fromYaw = from.getYaw() % 360;
float fromPitch = from.getPitch() % 360;
// making sure the center location is within range
if (hitBox.getCenter().distanceSquared(from) > Math.pow(data.getDistanceToTravel(), 2)) {
continue;
}
/* TODO Only allow hits on parts of the rectangle that are within range,
* not just the whole thing if the center is within range. */
// accounting for wind speed/direction
float windCompassDirection = data.getWindCompassDirection(from.getWorld());
float windSpeed = data.getWindSpeedMPH(from.getWorld());
fromYaw += (windCompassDirection > fromYaw ? 1 : windCompassDirection < fromYaw ? -1 : 0) * windSpeed;
fromYaw %= 360;
int[] orderClockwise = new int[] {0, 1, 4, 3};
Location thisSideCorner = hitBox.getCorner(0);
Location oppositeSideCorner = hitBox.getCorner(0);
for (int i = 0; i < orderClockwise.length; i++) {
int num = orderClockwise[i];
Location corner = hitBox.getCorner(num);
Location clockwise = hitBox.getCorner(orderClockwise[(i + 1) % 3]);
if ((Math.atan2(from.getZ() - corner.getZ(), from.getX() - corner.getX()) * 180 / Math.PI) > 0 && corner.distanceSquared(from) < clockwise.distanceSquared(from)) {
thisSideCorner = corner;
int exitCornerClockwiseAmount = (Math.atan2(from.getZ() - clockwise.getZ(), from.getX() - clockwise.getX()) * 180 / Math.PI) < 0 ? 2 : 3;
oppositeSideCorner = hitBox.getCorner((i + exitCornerClockwiseAmount) % 3);
}
}
Location entrance = getProjectileLocation(thisSideCorner, data, hitBox, fromYaw, fromPitch);
double distance = entrance.distance(from);
double deltaX = data.getDeltaX(distance, fromYaw);
double deltaY = data.getDeltaY(distance, fromPitch);
double deltaZ = data.getDeltaZ(distance, fromYaw);
entrance.add(deltaX, deltaY, deltaZ);
Location exit = getProjectileLocation(oppositeSideCorner, data, hitBox, deltaX, deltaY, deltaZ, fromYaw, fromPitch);
// hit detection and reaction
boolean hitX = entrance.getX() <= hitBox.getHighestX() && entrance.getX() >= hitBox.getLowestX();
boolean hitY = entrance.getY() <= hitBox.getHighestY() && entrance.getY() >= hitBox.getLowestY();
boolean hitZ = entrance.getZ() <= hitBox.getHighestZ() && entrance.getZ() >= hitBox.getLowestZ();
if (hitX && hitY && hitZ) {
hits.add(new Hit(from, entrance, exit, hitBox, data));
}
}
return hits;
}
private Location getProjectileLocation(Location thisSideCorner, ShotData data, HitBox hitBox, float fromYaw, float fromPitch) {
return getProjectileLocation(thisSideCorner, data, hitBox, 0, 0, 0, fromYaw, fromPitch);
}
private Location getProjectileLocation(Location thisSideCorner, ShotData data, HitBox hitBox, double addX, double addY, double addZ, float fromYaw, float fromPitch) {
double deltaFromToSideCornerX = thisSideCorner.getX() - from.getX();
double deltaFromToSideCornerY = thisSideCorner.getY() - from.getY();
double deltaFromToSideCornerZ = thisSideCorner.getZ() - from.getZ();
double xzDistFromSideCorner = Math.sqrt(Math.pow(deltaFromToSideCornerX, 2) + Math.pow(deltaFromToSideCornerZ, 2));
double yawToSideCorner = Math.atan2(deltaFromToSideCornerX, deltaFromToSideCornerZ) * 180 / Math.PI;// flipped x and z from normal
double theta1 = yawToSideCorner - fromYaw;
double theta2 = yawToSideCorner - theta1;
double outerAngle = 180 - yawToSideCorner - 90;// previously theta1
double outerAngleInShotCone = outerAngle + 90 + hitBox.getYawRotation();
double lastAngleInShotCone = 180 - theta1 - outerAngleInShotCone;
double xzDistanceFromHit = (xzDistFromSideCorner * Math.sin(Math.toRadians(outerAngleInShotCone))) / Math.sin(Math.toRadians(lastAngleInShotCone));
double deltaX = xzDistanceFromHit * Math.sin(Math.toRadians(theta2));// leaves out sin 90 because its just equal to 1...
double deltaZ = xzDistanceFromHit * Math.sin(Math.toRadians(90 - theta2));// leaves out sin 90 because its just equal to 1...
double xyzDistFromSideCorner = Math.sqrt(Math.pow(xzDistFromSideCorner, 2) + Math.pow(deltaFromToSideCornerY, 2));
double theta3 = Math.atan2(Math.abs(deltaFromToSideCornerY), xzDistFromSideCorner) * 180 / Math.PI;
double theta4 = Math.abs(fromPitch) - theta3;
double theta5 = 90 + theta3;
double theta6 = 180 - theta4 - theta5;
double hitDistance = (xyzDistFromSideCorner * Math.sin(Math.toRadians(theta5))) / Math.sin(Math.toRadians(theta6));
double deltaY = hitDistance * Math.sin(Math.toRadians(Math.abs(fromPitch)));// leaves out sin 90 because its just equal to 1...
if (deltaFromToSideCornerX < 0 && deltaX > 0) {
deltaX *= -1;
}
if (fromPitch > 0 && deltaY > 0) {// pitch in minecraft is backwards, normally it would be fromPitch < 0
deltaY *= -1;
}
if (deltaFromToSideCornerZ < 0 && deltaZ > 0) {
deltaZ *= -1;
}
Location hit = from.clone().add(deltaX + addX, deltaY + addY, deltaZ + addZ);
hit.setYaw(fromYaw);
hit.setPitch(fromPitch);
return hit;
}
HitBox.java:
private float yawRotation;
private double x, y, z;
private double[][] additions;
private Location center;
private Location[] corners = new Location[8];
private List<DataZone> dataZones = new ArrayList<DataZone>();
private UUID uuid = UUID.randomUUID();
//#formatter:off
/*
* O = origin
* X = x-axis
* Y = y-axis
* Z = z-axis
* C = center
*
* ---------------------
* / /|
* / / |
* Y-------------------- |
* | 90 | | 0 yaw
* | ^ | | /
* | | | |
* | | | | /
* | HEIGHT C | |
* | | | |/
* | | | Z
* | v | /
* | <---WIDTH---> |/<---LENGTH
* O-------------------X - - - - - - - - - -270 yaw
*/
/**
* An invisible box in the world that can be hit with a shot.
* Additionally, {#link DataZone} instances can be added to this,
* allowing for different damage and thickness on an area of the box.
*
* #param center The center of the hit box
* #param length The length (z axis) of the hit box
* #param width The width (x axis) of the hit box
* #param height The height (y axis) of the hit box
* #param yawRotation The rotation around the center of the origin (or any other point)
*/
public HitBox(Location center, double length, double width, double height, float yawRotation) {
corners[0] = center.clone().add(-1 * width / 2, -1 * height / 2, -1 * length / 2);
this.center = center;
this.x = width;
this.y = height;
this.z = length;
rotate(yawRotation);
}
//#formatter:on
public Location[] getCorners() {
return corners;
}
public Location getCorner(int corner) {
return corners[corner];
}
public Location getOrigin() {
return corners[0];
}
public void update() {};
public boolean isZoneOpen(DataZone zone) {
for (DataZone placed : dataZones) {
boolean Xs = overlap_1D(placed.xFrom, placed.xTo, zone.xFrom, zone.xTo);
boolean Ys = overlap_1D(placed.yFrom, placed.yTo, zone.yFrom, zone.yTo);
boolean Zs = overlap_1D(placed.zFrom, placed.zTo, zone.zFrom, zone.zTo);
if (Xs && Ys && Zs) {
return true;
}
}
return false;
}
public void rotate(float degrees) {
Location origin = corners[0];
this.yawRotation = (yawRotation + degrees) % 360;
additions = new double[][] { {0, 0, 0}, {x, 0, 0}, {0, y, 0}, {0, 0, z}, {x, 0, z}, {x, y, 0}, {x, y, z}, {0, y, z}};
for (int i = 0; i < 8; i++) {
double[] addition = additions[i];
double xPrime = center.getX() + (center.getX() - (origin.getX() + addition[0])) * Math.cos(Math.toRadians(yawRotation)) - (center.getZ() - (origin.getZ() + addition[2])) * Math.sin(Math.toRadians(yawRotation));
double zPrime = center.getZ() + (center.getX() - (origin.getX() + addition[0])) * Math.sin(Math.toRadians(yawRotation)) + (center.getZ() - (origin.getZ() + addition[2])) * Math.cos(Math.toRadians(yawRotation));
corners[i] = new Location(center.getWorld(), xPrime, origin.getY() + addition[1], zPrime, yawRotation, 0);
}
}
public void move(Location center) {
double deltaX = center.getX() - this.center.getX();
double deltaY = center.getY() - this.center.getY();
double deltaZ = center.getZ() - this.center.getZ();
for (int i = 0; i < 8; i++) {
corners[i].add(deltaX, deltaY, deltaZ);
}
this.center = center;
}
protected void setY(double y) {
int[] toChange = new int[] {2, 5, 6, 7};
for (int i : toChange) {
corners[i].setY(corners[0].getY() + y);
}
this.y = y;
}
public double getHighestX() {
double highestX = Double.MIN_VALUE;
for (Location location : corners) {
if (location.getX() > highestX) {
highestX = location.getX();
}
}
return highestX;
}
public double getHighestY() {
return corners[0].getY() + y;
}
public double getHighestZ() {
double highestZ = Double.MIN_VALUE;
for (Location location : corners) {
if (location.getZ() > highestZ) {
highestZ = location.getZ();
}
}
return highestZ;
}
public double getLowestX() {
double lowestX = Double.MAX_VALUE;
for (Location location : corners) {
if (location.getX() < lowestX) {
lowestX = location.getX();
}
}
return lowestX;
}
public double getLowestY() {
return corners[0].getY();
}
public double getLowestZ() {
double lowestZ = Double.MAX_VALUE;
for (Location location : corners) {
if (location.getZ() < lowestZ) {
lowestZ = location.getZ();
}
}
return lowestZ;
}
public float getYawRotation() {
return yawRotation;
}
Perhaps consider drawing a line following the same vector that your bullet travels along, this will provide a visual indicator for what is happening, pass in the same calculations and etc.
As other have mentioned also include lots of debug printouts. Hopefully once you have a visual cue you can see when/where the problem calculations are occuring.
Also you should aim to use a standard data type for calculations, a float or a double, NOT both as this can cause some weird rounding and calculation problems.
I know this is extremely late (almost 5 years in fact), but I developed a solution a few weeks after this question. After revisiting StackOverflow I decided to provide my solution for any who may find it useful.
The issue with the massive wall of code found in the question is that many values are being computed and every computation loses precision, resulting in some degree of variation (like I said, +/-5 blocks).
The solution source may be found here:
https://github.com/JamesNorris/MCShot
To compute intersect (found in util/Plane3D.java):
public Vector getIntersect(LineSegment3D segment) {
Vector u = segment.getEnd().subtract(segment.getStart());
Vector w = segment.getStart().subtract(point);
double D = normal.dot(u);
double N = -normal.dot(w);
if (Math.abs(D) < .00000001) {
/* if N == 0, segment lies in the plane */
return null;
}
double sI = N / D;
if (sI < 0 || sI > 1) {
return null;
}
return segment.getStart().add(u.multiply(sI));
}
As you can see, this requires way fewer computations and as a result provides much greater precision. This function gets the intersection of a 3D plane. A 3D plane is constructed using the values:
point - some point found within the plane normal - the normal
vector of the plane
I hope someone finds this solution helpful, or at least can use it as a lesson in computational precision!