I am working on a Java 2D video game, and I am looking for a faster Collision Detection algorithm than what you see below. I am trying to detect a collision between a torpedo and a ship. The algorithm I am using seriously impacts the performance of my main Graphics 2D dispatch loop, slowing down my screen repaints. Was wondering if anyone could recommend a better algorithm for how to handle this, that was quicker to detect a collision? Pointers to sample code would be great!
Here is the slow algorithm that I am using that goes Pixel by Pixel..
private boolean isPixelCollide(double x1, double y1, VolatileImage image1,
double x2, double y2, VolatileImage image2) {
double width1 = x1 + image1.getWidth() -1,
height1 = y1 + image1.getHeight() -1,
width2 = x2 + image2.getWidth() -1,
height2 = y2 + image2.getHeight() -1;
int xstart = (int) Math.max(x1, x2),
ystart = (int) Math.max(y1, y2),
xend = (int) Math.min(width1, width2),
yend = (int) Math.min(height1, height2);
// intersection rect
int toty = Math.abs(yend - ystart);
int totx = Math.abs(xend - xstart);
for (int y=1;y < toty-1;y++){
int ny = Math.abs(ystart - (int) y1) + y;
int ny1 = Math.abs(ystart - (int) y2) + y;
for (int x=1;x < totx-1;x++) {
int nx = Math.abs(xstart - (int) x1) + x;
int nx1 = Math.abs(xstart - (int) x2) + x;
try {
if (((image1.getSnapshot().getRGB(nx,ny) & 0xFF000000) != 0x00) &&
((image2.getSnapshot().getRGB(nx1,ny1) & 0xFF000000) != 0x00)) {
// collide!!
return true;
}
} catch (Exception e) {
// System.out.println("s1 = "+nx+","+ny+" - s2 = "+nx1+","+ny1);
}
}
}
return false;
}
You can define bounds of each object on your scene with Rectangle class and then use Rectangle#intersect(Rectangle rect) method. So your intersect method can look like this:
private boolean intersect(double x1, double y1, VolatileImage image1, double x2, double y2, VolatileImage image2) {
return (new Rectangle(x1, y1, image1.getWidth(), image1.getHeight()).intersect(new Rectangle(x2, y2, image2.getWidth(), image2.getHeight()));
}
That's a lot of getRGB(). Have you considered representing the torpedo and a ship as boxes first and use it for some early reject mechanism?
Also try dividing the pixel tests by 2. Test half the resolution by skipping along every other pixel. Half resolution shouldn't be much of a problem depending on your picture.
Related
Below example demonstrates "gaps" in drawing due to mouse events arriving too slowly (I presume). I'd like to capture all "mouseovered" pixels, without drawing lines from the last pixel written to the current one. Doing this assumes the mouse moved in a straight line, and that may not be the case for very fast movements.
Do we have any workarounds to get the missing events?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.awt.*;
public class DrawTest extends Application {
private static Point point;
#Override
public void start(Stage stage) {
Canvas canvas = new Canvas(800, 600);
AnchorPane anchorPane = new AnchorPane(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
point = MouseInfo.getPointerInfo().getLocation();
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 800, 600);
canvas.setOnMouseDragged((event) -> {
gc.getPixelWriter().setColor((int) event.getX(), (int) event.getY(), Color.BLACK);
System.out.println("X " + event.getX() + " Y " + event.getY());
});
stage.setScene(new Scene(anchorPane));
stage.show();
}
public static void main(String[] args) {
Application.launch(DrawTest.class, args);
}
}
This is an example of some of the event coordinates that get rendered with large gaps:
X 189.0 Y 248.0
X 193.0 Y 248.0
X 199.0 Y 248.0
X 204.0 Y 247.0
X 211.0 Y 244.0
X 225.0 Y 240.0
I've tried using this flag but it didn't help (on Linux): -Djavafx.animation.fullspeed=true
I'd be willing to try limited Swing options or maybe even more "native" workarounds, but obviously would prefer a JFX resolution or at least an explanation why JFX doesn't do this. Would need a Linux compatible solution, Windows too I guess. :P
This question is very similar, but the solutions proposed are still of a "connect the dots" nature and not exactly suitable if you need to accurately record or react to the pixel-by-pixel mouse path, so please don't close this as a duplicate - it's not.
How to draw a continuous line with mouse on JavaFX canvas?
Edit:
Well... it turns out I guess JavaFX is vindicated after all.
After adding the following bit in the middle of the example above, if you'll forgive the expected thread racing, it does look to me like JFX is matching AWT event for event with this crude polling method. Probably the best that can be done is to fill in the gaps somehow.
Thread t = new Thread(() -> {
while (true) {
Point p = MouseInfo.getPointerInfo().getLocation();
if (point.x != p.x || point.y != p.y) {
point = p;
System.out.println(point);
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
});
t.setDaemon(true);
t.start();
Output:
java.awt.Point[x=2890,y=301]
X 2890.0 Y 301.0
java.awt.Point[x=2884,y=303]
X 2884.0 Y 303.0
java.awt.Point[x=2870,y=308]
X 2870.0 Y 308.0
X 2848.0 Y 314.0
java.awt.Point[x=2848,y=314]
java.awt.Point[x=2822,y=322]
X 2822.0 Y 322.0
X 2790.0 Y 330.0
java.awt.Point[x=2790,y=330]
java.awt.Point[x=2760,y=338]
X 2760.0 Y 338.0
X 2726.0 Y 344.0
java.awt.Point[x=2726,y=344]
X 2694.0 Y 350.0
java.awt.Point[x=2694,y=350]
X 2668.0 Y 356.0
java.awt.Point[x=2668,y=356]
java.awt.Point[x=2646,y=360]
X 2646.0 Y 360.0
java.awt.Point[x=2631,y=366]
X 2631.0 Y 366.0
X 2623.0 Y 367.0
java.awt.Point[x=2623,y=367]
X 2620.0 Y 369.0
java.awt.Point[x=2620,y=369]
java.awt.Point[x=2621,y=369]
X 2621.0 Y 369.0
X 2622.0 Y 368.0
java.awt.Point[x=2622,y=368]
Since I've asked if 1) this can be prevented (presumably not) and 2) for workarounds, I feel like I should at least supply the basis for the workaround to this that I'll probably be settling on.
Since a "connect the dots" method is the only way, but I don't want to draw directly to the Canvas, I can't use the GraphicsContext line drawing calls in the other linked answer, which appear to invoke external native code for drawing lines and such. So we'll have to implement our own line drawing algorithm (maybe there's some library out there to do this?)...
This early draft workaround involves drawing an integer color to a generic buffer in memory (this could be a WritableImage in JFX or just a one dimensional int[] buffer etc.) so that the written values can easily be read back later, rather than drawing directly to a Canvas.
/*
* Bresenham's algorithm
*/
public static void drawLine(RasterLayer layer, int x0, int y0, int x1, int y1, int color) {
if (Math.abs(y1 - y0) < Math.abs(x1 - x0)) {
if (x0 > x1) {
plotLineLow(layer, x1, y1, x0, y0, color);
} else {
plotLineLow(layer, x0, y0, x1, y1, color);
}
} else {
if (y0 > y1) {
plotLineHigh(layer, x1, y1, x0, y0, color);
} else {
plotLineHigh(layer, x0, y0, x1, y1, color);
}
}
}
private static void plotLineLow(RasterLayer layer, int x0, int y0, int x1, int y1, int color) {
int dx = x1 - x0;
int dy = y1 - y0;
int yi = 1;
if (dy < 0) {
yi = -1;
dy = -dy;
}
int D = (2 * dy) - dx;
int y = y0;
for (int x = x0; x < x1; x++) {
layer.setPixel(x, y, color);
if (D > 0) {
y = y + yi;
D = D + (2 * (dy - dx));
} else {
D = D + 2 * dy;
}
}
}
private static void plotLineHigh(RasterLayer layer, int x0, int y0, int x1, int y1, int color) {
int dx = x1 - x0;
int dy = y1 - y0;
int xi = 1;
if (dx < 0) {
xi = -1;
dx = -dx;
y1++;
}
int D = (2 * dx) - dy;
int x = x0;
for (int y = y0; y < y1; y++) {
layer.setPixel(x, y, color);
if (D > 0) {
x = x + xi;
D = D + (2 * (dx - dy));
} else {
D = D + 2 * dx;
}
}
}
To make the drawn line thicker I suppose I'll just have to draw additional adjacent lines by offsetting the x or y coordinates for the additional lines, selected depending on the slope of the line. (Draw additional adjacent lines with offset y values for more horizontal lines, and offset x values for more vertical lines.) Testing so far this seems to work reasonably well. Obviously you could also get fancy with anti-aliased lines and such...
I am unable to create several instances of the waveClock object even though I have put it in an array and marked the centre positions for each object. I would like to create 4 objects in one window, all responding to different sound frequencies/beat onsets etc
Could someone shed some light on how to go about this? I believe it may be an issue with the centerX and centerY variables in the waveClock class
ArrayList<waveClock> waveClocks = new ArrayList<waveClock>();
//global variables
float angnoise, radiusnoise;
float xnoise, ynoise;
float angle = -PI/6;
float radius;
float strokeCol = 254;
int strokeChange = -1;
int speed; //changes speed of visualisation once beat is detected?
void setup()
//for every waveClock we need 180 pixels width, then add 20 pixels for first gap
size(740, 650);
background(255);
//code is called
waveClocks.add(new waveClock(100, height/2, minRadius, bassColour, lowBassBand, highBassBand, numberOfLowOnsetsThreshold));
waveClocks.add(new waveClock(280, height/2, minRadius, midColour, lowMidBand, highMidBand, numberOfMidOnsetsThreshold));
waveClocks.add(new waveClock(460, height/2, minRadius, highColour, lowHighBand, highHighBand, numberOfHighOnsetsThreshold));
waveClocks.add(new waveClock(640, height/2, minRadius, veryHighColour, lowVeryHighBand, highVeryHighBand, numberOfVeryHighOnsetsThreshold));
//set the min and max radius of each of the viz circles
/* for (int i = 0; i < waveClocks.size(); i++) {
//go through the arraylist of waveClocks and set the min and max radius of each circle
waveClocks.get(i).setMinMaxRadius(minRadius, maxRadius);
}*/
song.play();
beat = new BeatDetect(song.bufferSize(), song.sampleRate());
bl = new BeatListener(beat, song);
}
void draw() {
//clear the screen by painting it black
//background(0);
for (int i = 0; i < waveClocks.size(); i++) {
//has there been a beat in the range? get(circle ID).low band, high band etc.
if (beat.isRange(waveClocks.get(i).getLowBand(), waveClocks.get(i).getHighBand(), waveClocks.get(i).getOnsetThreshold())) {
waveClocks.get(i).setMaxRadius();
}
//waveClocks.get(i).drawCircle();
waveClocks.get(i).drawWaveClock();
}
}
waveClock class in a separate tab
//class is an architecture blueprint
//objects are the actual buildings built from the methods (can make as many as you like)
//constructor is the builder/constructor literally
class waveClock {
float centerX; //co-ordinates of circle's position
float centerY; //co-ordinates of circle's position
float radius; //avg radius
// float minRadius; //smallest size it can be
// float maxRadius; //biggest size it can be
color col; //colour
int onsetThreshold; //
int lowBand; //looks at lowest band of frequency and makes circle sensitive to it
int highBand; //looks at highest band of frequency and makes circle sensitive to it
boolean onset; //has there been an onset (beat has occurred or not?)
//the constructor
waveClock(float x, float y, float r, color c, int lb, int hb, int t) {
centerX = x;
centerY = y;
radius = r;
col = c;
lowBand = lb;
highBand = hb;
onsetThreshold = t;
}
void drawWaveClock() {
radiusnoise += 0.005;
radius = (noise(radiusnoise)*350) + 1;
angnoise += 0.005;
angle += (noise(angnoise)*6) - 3;
if (angle > 360) {
angle -= 360;
} else if (angle < 0) {
angle += 360;
}
xnoise += 0.01;
ynoise =+ 0.01;
float centerX = width/2 + (noise(xnoise)*100) - 50;
float centerY = height/2 + (noise(ynoise)*100) - 50;
float rad = radians(angle);
float x1 = centerX + (radius*cos(rad));
float y1 = centerY + (radius*sin(rad));
float opprad = rad + PI;
float x2 = centerX + (radius*cos(opprad));
float y2 = centerY + (radius*sin(opprad));
strokeCol += strokeChange;
if (strokeCol > 354) {
strokeChange = -1;
} else if (strokeCol < 0) {
strokeChange = 1;
}
stroke(strokeCol, 60);
strokeWeight(1);
line(x1, y1, x2, y2);
}
}
You aren't ever using the class-level centerX and centerY variables. Instead, you're recalculating a new centerX and centerY in the drawWaveClock() function.
float centerX = width/2 + (noise(xnoise)*100) - 50;
float centerY = height/2 + (noise(ynoise)*100) - 50;
These are all drawn from the center of the screen, so the waves will end up in the same position.
In the future, please try to narrow your problem down to a MCVE that demonstrates the problem. Also please use proper naming conventions- classes start with an upper-case letter, for example. Good luck.
I have recently been playing around with Bicubic Interpolation, as I am wanting to generate the earth, based on real heightmaps inside Minecraft. The reason I am using the interpolation, is because I would like to make the world have more detail. After a lot of research, and a lot of trial and error, I decided to come ask here. :)
Because of limited memory, I can't scale the image on startup, and keep that loaded, I have to do the interpolation on the fly.
I seem to have gotten Cubic Interpolation to work, as seen here:
Visualisation of the interpolation
However, I can not get Bicubic Interpolation to work. For testing purposes, I am using a small image, and scaling it by 4. This is what the code does: Input -> Output
This is my current code:
public static double cubicInterpolate(double[] points, double x, double scale)
{
x /= scale;
double inBetweenPoint = x;
int xInHeightmap = (int) x;
inBetweenPoint -= xInHeightmap;
double beforePoint1 = safe(points, xInHeightmap - 1);
double point1 = safe(points, xInHeightmap);
double point2 = safe(points, xInHeightmap + 1);
double afterPoint2 = safe(points, xInHeightmap + 2);
double p = (afterPoint2 - point2) - (beforePoint1 - point1);
double q = (beforePoint1 - point1) - p;
double r = point2 - beforePoint1;
double s = point1;
return (p * Math.pow(inBetweenPoint, 3)) + (q * Math.pow(inBetweenPoint, 2)) + (r * inBetweenPoint) + s;
}
public static double bicubicInterpolate(double[][] points, double x, double y, double scale)
{
x /= scale;
double inBetweenPoint = x;
int xInHeightmap = (int) x;
inBetweenPoint -= xInHeightmap;
double beforePoint1 = cubicInterpolate(safe(points, xInHeightmap - 1), y, scale);
double point1 = cubicInterpolate(safe(points, xInHeightmap), y, scale);
double point2 = cubicInterpolate(safe(points, xInHeightmap + 1), y, scale);
double afterPoint2 = cubicInterpolate(safe(points, xInHeightmap + 2), y, scale);
return cubicInterpolate(new double[]{beforePoint1, point1, point2, afterPoint2}, inBetweenPoint + 1, scale);
}
public static double[] safe(double[][] p, int i)
{
return p[Math.max(0, Math.min(i, p.length - 1))];
}
public static double safe(double[] p, int i)
{
return p[Math.max(0, Math.min(i, p.length - 1))];
}
Thank you for your help :)
To my understanding, your implementaion treats the given x and y coordinates in a totally different way, which does not lead to the desired result. You should basically do the following.
First, you need to identify the four points (coordinate pairs) in the grid which will form the basis of the interpolation, as well as the distances in both directions from the grid to to the interpolation. This can be done as follows.
int xfloor = (int)x;
int yfloor = (int)y;
int xdelta = x - (double)xfloor;
int ydelta = y - (double)yfloor;
The desired coordinate pairs are then (depending on the orientation of the axes)
P1 = (xfloor, yfloor ) // left upper corner
P2 = (xfloor, yfloor + 1) // left lower corner
P3 = (xfloor + 1 ,yfloor + 1) // right lower corner
P4 = (xfloor + 1, yfloor ) // left upper corner
and finally you wouöd first interpolate along parallel axes and then in the middle, which can be done as follows using intermediate values.
val1 = cubic(value(P1), value(P2), deltay) // interpolate cubically on the left edge
val2 = cubic(value(P4), value(P3), deltay) // interpolate cubically on the right edge
val = cubic (val1, val2, deltax) // interpolate cubically between the intermediates
Interpolation methods are also discussed here.
In your bicubic-interpolation method you write:
//double inBetweenPoint = x;
//int xInHeightmap = (int) x;
//inBetweenPoint -= xInHeightmap;
return cubicInterpolate(new double[]{beforePoint1, point1, point2, afterPoint2}, inBetweenPoint, scale);
As you can easily see, inBetweenPoint will be in the interval [0, 1) at the call to cubicInterpolate. This means that interpolation will be between beforePoint1 and point1. not, as wanted, between point1 and point2.
The simple fix is writing
return cubicInterpolate(new double[]{beforePoint1, point1, point2, afterPoint2}, inBetweenPoint + 1, scale);
I am having an annoying problem trying to resize a draggable square in Java. To resize the square I am checking to see if the mouse cursor is inside a rectangular area surrounding the bottom right hand corner of the square - if it is, then the square resizes when I drag the mouse. This code works fine, BUT if I then click on the square to drag it around the screen, it jumps back to the default size of 100 x 100.
My class starts off like this:
public class DragPanel extends JPanel implements MouseListener,
MouseMotionListener, MouseWheelListener
{
Graphics2D g2;
Rectangle2D square;
Color colour;
double x1, y1, x2, y2, sizex, sizey;
double offsetX, offsetY;
double oldx, oldy;
boolean dragging = false;
boolean resizing = false;
public DragPanel()
{
x1 = 10.0;
y1 = 10.0;
sizex = 100.0;
sizey = 100.0;
x2 = x1 + sizex;
y2 = y1 + sizey;
square = new Rectangle2D.Double(x1, y1, sizex, sizey);
colour = Color.BLUE;
setFocusable(true);
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
this.requestFocus();
}
Here are my Mouse Pressed and Mouse Dragged Methods:
Mouse Pressed:
#Override
public void mousePressed(MouseEvent ev)
{
double mx = ev.getX();
double my = ev.getY();
if (mx > x1 && mx < x2 && my > y1 && my < y2)
{
dragging = true;
offsetX = mx - x1;
offsetY = my - y1;
}
if (mx > x2 - 3 && mx < x2 + 3 && my > y2 - 3 && my < y2 + 3)
{
oldx = mx;
oldy = my;
resizing = true;
}
}
Mouse Dragged:
#Override
public void mouseDragged(MouseEvent ev)
{
if (dragging)
{
double mx = ev.getX();
double my = ev.getY();
x1 = mx - offsetX;
y1 = my - offsetY;
x2 = x1 + sizex;
y2 = y1 + sizey;
square = new Rectangle2D.Double(x1, y1, sizex, sizey);
repaint();
}
if (resizing)
{
double mx = ev.getX();
double my = ev.getY();
double diffx, diffy;
diffx = oldx - mx;
diffy = oldy - my;
square = new Rectangle2D.Double(x1, y1, sizex - diffx, sizey - diffy);
repaint();
}
}
The code you see above does what I want it to do in terms of resizing, but as I already explained, DOESN'T work properly when I try and drag the square after resizing. And I know why - it is because of THIS line:
square = new Rectangle2D.Double(x1, y1, sizex - diffx, sizey - diffy);
So my solution was to change the last part of the if (resizing) block as follows:
diffx = oldx - mx;
diffy = oldy - my;
sizex -= diffx;
sizey -= diffy;
square = new Rectangle2D.Double(x1, y1, sizex, sizey);
repaint();
When I do this however, the thing stops working! I can still drag the square, but if I try and resize, the movement is erratic and extreme.
WHAT am I doing wrong?
Seems to me that the conditions for dragging and resizing are not mutually exclusive, particularly close to the borders. Assuming you are putting these two flags to false on mouse release, a single mouse click or move could still go into both modes at once.
I would put else if (mx > x2 - 3 && mx < x2 + 3 && my > y2 - 3 && my < y2 + 3) and else if (resizing) to avoid any intended consequences. This would make any dragging a priority over any resizing.
Being a graphical thing, hard to say if this is your problem (or part of it), but give it can't hurt to clear this up.
Edit: Of course, you might want to do the resize conditions take priority over (go before) the drag condition, since the area for resizing is much smaller than the area for dragging.
Edit 2: BTW, when you resize you are modifying your square, but not your sizex and sizey variables. Naturally, when you ask for those values when dragging they are the same as before (100). Store the changes and it should work. On that note, it is probably a bad idea to have multiple representations of the same information. It causes redundancies and synchronization issues like this one. If possible, remove these variables: double x1, y1, x2, y2, sizex, sizey; and read/write then from the current state of square (you probably don't need a new square every time either).
I treat these a two separate functions:
Check out the Component Mover
and the Component Resizer
The code in these classes is more complex than you need but you may want to consider the separation of the logic for each function in your final solution.
I found the solution, or rather, I found a solution. I changed my if (resizing) statement within the mouseDragged method as follows:
Old:
if (resizing)
{
double mx = ev.getX();
double my = ev.getY();
double diffx, diffy;
diffx = oldx - mx;
diffy = oldy - my;
square = new Rectangle2D.Double(x1, y1, sizex - diffx, sizey - diffy);
repaint();
}
New:
if (resizing)
{
double mx = ev.getX();
double my = ev.getY();
x2 = mx - rOffX;
y2 = my - rOffX;
sizex = x2 - x1;
sizey = sizex;
square.setRect(x1, y1, sizex, sizey);
repaint();
}
I need a function which takes a line (known by its coordinates)
and return a line with same angle, but limited to certain length.
My code gives correct values only when the line is turned 'right'
(proven only empirically, sorry).
Am I missing something?
public static double getAngleOfLine(int x1, int y1, int x2, int y2) {
double opposite = y2 - y1;
double adjacent = x2 - x1;
if (adjacent == Double.NaN) {
return 0;
}
return Math.atan(opposite / adjacent);
}
// returns newly calculated destX and destY values as int array
public static int[] getLengthLimitedLine(int startX, int startY,
int destX, int destY, int lengthLimit) {
double angle = getAngleOfLine(startX, startY, destX, destY);
return new int[]{
(int) (Math.cos(angle) * lengthLimit) + startX,
(int) (Math.sin(angle) * lengthLimit) + startY
};
}
BTW: I know that returning arrays in Java is stupid,
but it's just for the example.
It would be easier to just treat it as a vector. Normalize it by dividing my its magnitude then multiply by a factor of the desired length.
In your example, however, try Math.atan2.
In Python because I don't have a Java compiler handy:
import math
def getLengthLimitedLine(x1, y1, x2, y2, lengthLimit):
length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
if length > lengthLimit:
shrink_factor = lengthLimit / length
x2 = x1 + (x2-x1) * shrink_factor
y2 = y1 + (y2-y1) * shrink_factor
return x2, y2
print getLengthLimitedLine(10, 20, 25, -5, 12)
# Prints (16.17, 9.71) which looks right to me 8-)
It's an easy problem if you understand something about vectors.
Given two points (x1, y1) and (x2, y2), you can calculate the vector from point 1 to 2:
v12 = (x2-x1)i + (y2-y2)j
where i and j are unit vectors in the x and y directions.
You can calculate the magnitude of v by taking the square root of the sum of squares of the components:
v = sqrt((x2-x2)^2 + (y2-y1)^2)
The unit vector from point 1 to point 2 equals v12 divided by its magnitude.
Given that, you can calculate the point along the unit vector that's the desired distance away by multiply the unit vector times the length and adding that to point 1.
Encapsulate Line in a class, add a unit method and a scale method.
public class Line {
private float x;
private float y;
public Line(float x1, float x2, float y1, float y2) {
this(x2 - x1, y2 - y1);
}
public Line(float x, float y) {
this.x = x;
this.y = y;
}
public float getLength() {
return (float) Math.sqrt((x * x) + (y * y));
}
public Line unit() {
return scale(1 / getLength());
}
public Line scale(float scale) {
return new Line(x * scale, y * scale);
}
}
Now you can get a line of arbitrary length l by calling
Line result = new Line(x1, x2, y1, y2).unit().scale(l);
No need to use trig, which can have some nasty edge cases. Just use similar triangles:
public static int[] getLengthLimitedLine(int startX, int startY,
int destX, int destY, int lengthLimit)
{
int deltaX = destX - startX;
int deltaY = destY - startY;
int lengthSquared = deltaX * deltaX + deltaY * deltaY;
// already short enough
if(lengthSquared <= lengthLimit * lengthLimit)
return new int[]{destX, destY};
double length = Math.sqrt(lengthSquared);
double newDeltaX = deltaX * lengthLimit / length;
double newDeltaY = deltaY * lengthLimit / length;
return new int[]{(int)(startX + newDeltaX), (int)(startY + newDeltaY)};
}
Just use the Pythagorean theorem, like so:
public static int[] getLengthLimitedLine(int start[], int dest[], int lengthLimit) {
int xlen = dest[0] - start[0]
int ylen = dest[1] - start[1]
double length = Math.sqrt(xlen * xlen + ylen * ylen)
if (length > lengthLimit) {
return new int[] {start[0], start[1],
start[0] + xlen / lengthLimit,
start[1] + ylen / lengthLimit}
} else {
return new int[] {start[0], start[1], dest[0], dest[1];}
}
}