I already searched over stackoverflow for similar questions, and tried to implement using some suggestions from other answers like this one:
Point p1 = prop.getDisplayPoint();
Point p2 = prop2.getDisplayPoint();
int xCenter = p1.x - 50;
int yCenter;
if(p1.y > p2.y)
yCenter = p1.y - ((p1.y-p2.y)/2);
else
yCenter = p2.y - ((p2.y-p1.y)/2);
int r = (int)Math.sqrt((p1.x-xCenter)*(p1.x-xCenter) + (p1.y-yCenter)*(p1.y-yCenter));
int x = xCenter-r;
int y = yCenter-r;
int width = 2*r;
int height = 2*r;
int startAngle = (int) ((180/Math.PI)*Math.atan2(p1.y-yCenter, p1.x-xCenter));
int endAngle = (int) ((180/Math.PI)*Math.atan2(p2.y-yCenter, p2.x-xCenter));
g.drawArc(x, y, width, height, startAngle, endAngle);
But still this didnt helped me.
I tried to compute the center of my two points, but the result of the arc was not what I was waiting for.
This was the result:
This is what I am looking for:
I have the coordinates of each pair of points I want to connect, like the point at "car" and the point at "bus" then I want to draw an arc between them. The angle will always be something like that.
Looking at the picture, it appears that you have two problems.
The first is that your origin point is incorrect. I haven't tried specific values, but I think it's because you're calculating an arbitrary center based on the first point, and then calculating the start and end of your arcs based on that center point (rather than the actual text locations).
However, I think that the bigger problem is that an arc isn't really appropriate to the task. With an arc, you could go for a semi-circle (or semi-oval), or maybe 1/3 of a circle, but those won't look very good. They certainly won't look like your desired example.
Instead, you want a Bezier Curve, so that you can deepen the sides of the "arc". Here's a SO question that points you to the docs for the bezier curve functions in Java2D. There are also a bunch of examples if you Google for "java draw bezier".
Related
I have tried to create NPC character that can "see" the player by using cones of vision.
The NPC will rotate back and forth at all times.
My problem is that the arc has a generic and unchanging position, but when its drawn to the screen it looks correct.
[Screenshots of the collisions in action][1]
[GitHub link for java files][2]
I'm using Arc2D to draw the shape like this in my NPC class
// Update the shapes used in the npc
rect.setRect(x, y, w, h);
ellipse.setFrame(rect);
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle, visionAngle * 2, Arc2D.PIE);
/ CenterX, CenterY (of the npc),
/ the distance from the arc to the npc
/ a constant value around 45 degrees and a constant value around 90 degress (to make a pie shape)
I've tried multiplying the position and the angles by the sin and cosine of the NPC's current angle
something like these
visionArc.setArcByCenter(cx * (Math.cos(Math.toRadians(angle))), cy (Math.sin(Math.toRadians(angle)), visionDistance, visionAngle, visionAngle * 2, Arc2D.PIE);
or
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle - angle, (visionAngle + angle) * 2, Arc2D.PIE);
or
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle * (Math.cos(Math.toRadians(angle))), visionAngle * 2, Arc2D.PIE);
I've tried a lot but can't seem to find what works. Making the vision angles not constant makes an arc that expands and contracts, and multiplying the position by the sin or cosine of the angle will make the arc fly around the screen, which doesn't really work either.
This is the function that draws the given NPC
public void drawNPC(NPC npc, Graphics2D g2, AffineTransform old) {
// translate to the position of the npc and rotate
AffineTransform npcTransform = AffineTransform.getRotateInstance(Math.toRadians(npc.angle), npc.x, npc.y);
// Translate back a few units to keep the npc rotating about its own center
// point
npcTransform.translate(-npc.halfWidth, -npc.halfHeight);
g2.setTransform(npcTransform);
// g2.draw(npc.rect); //<-- show bounding box if you want
g2.setColor(npc.outlineColor);
g2.draw(npc.visionArc);
g2.setColor(Color.BLACK);
g2.draw(npc.ellipse);
g2.setTransform(old);
}
This is my collision detection algorithim - NPC is a superclass to ninja (Shorter range, higher peripheral)
public void checkNinjas(Level level) {
for (int i = 0; i < level.ninjas.size(); i++) {
Ninja ninja = level.ninjas.get(i);
playerRect = level.player.rect;
// Check collision
if (playerRect.getBounds2D().intersects(ninja.visionArc.getBounds2D())) {
// Create an area of the object for greater precision
Area area = new Area(playerRect);
area.intersect(new Area(ninja.visionArc));
// After checking if the area intersects a second time make the NPC "See" the player
if (!area.isEmpty()) {
ninja.seesPlayer = true;
}
else {
ninja.seesPlayer = false;
}
}
}
}
Can you help me correct the actual positions of the arcs for my collision detection? I have tried creating new shapes so I can have one to do math on and one to draw to the screen but I scrapped that and am starting again from here.
[1]: https://i.stack.imgur.com/rUvTM.png
[2]: https://github.com/ShadowDraco/ArcCollisionDetection
After a few days of coding and learning and testing new ideas I came back to this program and implemented the collision detection using my original idea (ray casting) and have created the equivalent with rays!
Screenshot of the new product
Github link to the project that taught me the solution
Here's the new math
public void setRays() {
for (int i = 0; i < rays.length; i++) {
double rayStartAngleX = Math.sin(Math.toRadians((startAngle - angle) + i));
double rayStartAngleY = Math.cos(Math.toRadians((startAngle - angle) + i));
rays[i].setLine(cx, cy, cx + visionDistance * rayStartAngleX, cy + visionDistance * rayStartAngleY);
}
}
Here is a link the the program I started after I asked this question and moved on to learn more, and an image to what the new product looks like
(The original github page has been updated with a new branch :) I'm learning git hub right now too
I do not believe that using Arc2D in the way I intended is possible, however there is .setArcByTangent method, it may be possible to use that but I wasn't going to get into that. Rays are cooler.
I'm trying to draw maps using Java2D. When my map is zoomed out my roads are full of drawing artefacts. This is a small part of the screen when drawing a complete US state:
This is a similar section of the road when zoomed closer in:
The line style used is a solid blue line with width scaled to be equivalent to 2 pixels.
I've tried various rendering hints and line joining rules and nothing seems to help.
I'm using Open JDK 1.7 on a Mac running the OS/X 10.8 and this is also reproducible on a Linux machine with a Sun JDK 1.6.
All shapes and transforms are double precision as far as possible with Java2D.
The geometry of the line has many closely spaced points and I suspect that the cause of the drawing artefacts is that the renderer is getting confused by consecutive points that are closer than a single pixel.
Is there a way to improve the appearance of the zoomed out shapes without thinning the points?
Edit
The drawing artefacts are at the points where separate line segments meet, so the missing pixels are something to do with the line caps (ends) not meeting, even when the end points are identical. This image shows the meeting point between two line segments. I have highlighted each line segment in a 7 pixel scaled line style (XOR-ed with white) but if you look very closely you can still see part of the original blue line (this is due to the rounded caps overlapping and the XOR draw mode.) At ordinary scales the ends seem to overlap, but when zoomed out and back in ordinary paint mode there is a broken line effect.
One workaround would be to join all the contiguous line segments together before drawing them, but I would still like to know the real cause of the drawing artefacts.
I am unable to recreate the situation you have using the OS X 1.6 JDK, but I still have some suggestions for you.
If you are just using this to outline states, consider using the GeneralPath class. You can use the lineTo(x,y) method to establish each of your points on the line. Again, because I can't recreate your problem using Line2D.Double, I don't know if this will actually be any different.
Second, and possibly more importantly, is how you are zooming in and out. I am using an AffineTransform (with setScaleTo(x,y)) on my Graphics2D object, and everything is working swimmingly. Compared to the alternative of scaling the points in your data by a zoom factor (or whatever else you could do), this is fairly easy. You'll also have to adjust the stroke of the lines by the factor, because it will scale everything down. I can post screenshots if you'd like.
Please check Xiaolin Wu's line algorithm it should answer you question!
Basic Concept
function plot(x, y, c) is
plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1)
function ipart(x) is
return integer part of x
function round(x) is
return ipart(x + 0.5)
function fpart(x) is
return fractional part of x
function rfpart(x) is
return 1 - fpart(x)
function drawLine(x1,y1,x2,y2) is
dx = x2 - x1
dy = y2 - y1
if abs(dx) < abs(dy) then
swap x1, y1
swap x2, y2
swap dx, dy
end if
if x2 < x1
swap x1, x2
swap y1, y2
end if
gradient = dy / dx
// handle first endpoint
xend = round(x1)
yend = y1 + gradient * (xend - x1)
xgap = rfpart(x1 + 0.5)
xpxl1 = xend // this will be used in the main loop
ypxl1 = ipart(yend)
plot(xpxl1, ypxl1, rfpart(yend) * xgap)
plot(xpxl1, ypxl1 + 1, fpart(yend) * xgap)
intery = yend + gradient // first y-intersection for the main loop
// handle second endpoint
xend = round (x2)
yend = y2 + gradient * (xend - x2)
xgap = fpart(x2 + 0.5)
xpxl2 = xend // this will be used in the main loop
ypxl2 = ipart (yend)
plot (xpxl2, ypxl2, rfpart (yend) * xgap)
plot (xpxl2, ypxl2 + 1, fpart (yend) * xgap)
// main loop
for x from xpxl1 + 1 to xpxl2 - 1 do
plot (x, ipart (intery), rfpart (intery))
plot (x, ipart (intery) + 1, fpart (intery))
intery = intery + gradient
end function
I am trying to compute a polygon that surrounds a line connecting multiple points (e.g. a GPX track).
The image below shows an example with the track as red line and the desired polygon in blue.
As simplification the red points are denoted by x and y - not by latitude/longitude.
How do I compute such an environment (light blue polygon) if I only have the list of the three points specifying the path?
Partial solutions (e.g. for only two points) or hints about mathematical libraries (in Java) that provide algorithms for such a computation would also bring me one step forward.
Further assumptions:
The track is intersection free.
Update:
Using the approach as presented by Rogach and xan I ran into some problems if the angle between the lines is smaller than 90 degree or larger than 270 degree:
As you can see the polygon gets intersects itself which leads to a serious problem.
From my point of view using an java.awt.geom.Area is the better approach:
My solution (based on the code by Rogach):
For each line connecting two points of the track I compute a surrounding polygon. Afterwards I add (area union) the computed polygon to an Area which does all the necessary computation for me. As the Area strictly uses the "or" algorithm on adding new polygons I do not have to care about "self intersections" of the polygon as presented in the update above.
Area area = new Area();
for (int i = 1; i < points.size(); i++) {
Point2D point1 = points.get(i - 1);
Point2D point2 = points.get(i);
Line2D.Double ln = new Line2D.Double(point1.getX(), point1.getY(), point2.getX(), point2.getY());
double indent = 15.0; // distance from central line
double length = ln.getP1().distance(ln.getP2());
double dx_li = (ln.getX2() - ln.getX1()) / length * indent;
double dy_li = (ln.getY2() - ln.getY1()) / length * indent;
// moved p1 point
double p1X = ln.getX1() - dx_li;
double p1Y = ln.getY1() - dy_li;
// line moved to the left
double lX1 = ln.getX1() - dy_li;
double lY1 = ln.getY1() + dx_li;
double lX2 = ln.getX2() - dy_li;
double lY2 = ln.getY2() + dx_li;
// moved p2 point
double p2X = ln.getX2() + dx_li;
double p2Y = ln.getY2() + dy_li;
// line moved to the right
double rX1_ = ln.getX1() + dy_li;
double rY1 = ln.getY1() - dx_li;
double rX2 = ln.getX2() + dy_li;
double rY2 = ln.getY2() - dx_li;
Path2D p = new Path2D.Double();
p.moveTo(lX1, lY1);
p.lineTo(lX2, lY2);
p.lineTo(p2X, p2Y);
p.lineTo(rX2, rY2);
p.lineTo(rX1_, rY1);
p.lineTo(p1X, p1Y);
p.lineTo(lX1, lY1);
area.add(new Area(p));
}
As I see, this problem is similar to polygon buffering problem.
I think following approach can help you:
For each segment of your track, find two lines - one to the left and one to the right.
Then, iterate for over your ofsetted lines, and resolve intersections. For example:
http://img25.imageshack.us/img25/7660/temprhk.png
Add caps to ends, and you're done! :)
And some code:
Moving a line to the left:
Line2D l;
double indent; // distance from central line
double dx = ln.getX2() - ln.getX1();
double dy = ln.getY2() - ln.getY1();
double length = ln.getP1().distance(ln.getP2());
double newX1 = l.getX1() - indent*(dy/length);
double newY1 = l.getY1() + indent*(dx/length);
double newX2 = l.getX2() - indent*(dy/length);
double newY2 = l.getY2() + indent*(dx/length);
Line2D leftLine = new Line2D.Double(newX1, newY1, newX2, newY2);
For moving it to the right, change "+" to "-" and vice versa in the last 4 lines of code.
About working with intersections - if two line segment intersect, you just output the intersection point. If they do not, then situation is a bit more complicated - you can, of course, still output the intersection, but in case of rapidly turning track, there will be strange outbursts. I inserted an arc segment in similar situation, but the code is to big and scattered, so I can't paste it here.
Or, you can do as you show on your picture - just connect end points.
And, by the way, if speed is not a big issue, you can use even better way - for each line of track, find left and right lines, add caps, pack it all into Path2D, then create Area from Path2D.
In such case, you can make this "line with caps" as intersection of three areas: rectangle, whose points are just end points of right and left line, and two circles with centers on original track segment ends.
When you compute Areas for all lines, just intersect them using Area add() method.
This approach deals with just any situations, even self-intersections and breaks in the track.
See my answer to a similar question, "How to draw an outline around any line."
Same idea as Rogach provides here, but perhaps different drawings and explanations will help clarify it.
If you don't want to write the code for the buffering as described by Rogach, JTS could do the magic for you.
See the developer guide for a quick introduction.
Half-baked suggestion: Calculate the normal to each segment. Then, for each vertex V_i, interpolate the normals from its adjacent segments to get n_i (normalise it again) and add two vertices at V_i +/- a*n_i where a is some scaling factor.
If you join these points, you won't get exactly your blue polygon, but it might be good enough.
You may have to keep track of which "side" the new vertices are on. If you can close the curve without self-intersections this just becomes a point in polygon test for each vertex.
How could I draw a quadratic curve or a trigonometric curve (such as sin(x)) on a Canvas?
Like you, I needed to draw a curved line from point(x1, y1) to point (x2, y2). I did some searching around which lead me to the Path class (android.graphics.Path). Path has numerous methods for drawing lines. Once you have created a path you use a draw method to make the actual line. The paths can be rotated, transformed, saved, and added to. There are arcs, circles, and rectangles that be drawn with this class too.
http://developer.android.com/reference/android/graphics/Path.html
Set start point of path → mPath.moveTo(x1, y1);
Set constant and end points → mPath.quadTo(cx, cy, x2, y2);
Convert path to line → canvas.drawPath(mPath, mPaint);
Here is a drawEquation() method I wrote for a Graph class - I think it may help. The basic idea to create a method that accepts an equation (which is basically just a function) like
function(x) = Math.sin(x);
and then loop through the bounds of the graph and draws small segments connecting each point. The transformContext() just inverts the canvas context so that increasing values of y go upwards and not downwards:
Graph.prototype.transformContext = function(){
var canvas = this.canvas;
var context = this.context;
// move context to center of canvas
this.context.translate(this.centerX, this.centerY);
// stretch grid to fit the canvas window, and
// invert the y scale so that that increments
// as you move upwards
context.scale(this.scaleX, -this.scaleY);
};
Graph.prototype.drawEquation = function(equation, color, thickness){
var canvas = this.canvas;
var context = this.context;
context.save();
this.transformContext();
context.beginPath();
context.moveTo(this.minX, equation(this.minX));
for (var x = this.minX + this.iteration; x <= this.maxX; x += this.iteration) {
context.lineTo(x, equation(x));
}
context.restore();
context.lineJoin = "round";
context.lineWidth = thickness;
context.strokeStyle = color;
context.stroke();
};
Most drawing APIs dont provide such functions, you will have to calculate the pixels of your desired curve in pixels and draw piece by piece on the canvas using one or more calls to the canvas API.
Use Canvas.drawPath and Path.quadTo.
I'm going to assume that you are familiar with drawing basic lines on a canvas, if not then respond back and we can delve further back. However, as far as just drawing a sine function there is a function within the Math class that has just what you need.
http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Math.html#sin%28double%29
From there you just need to pass your x variable(in radians) into the function and save it's output as a y variable. This represent a point on your graph. Now increment the x1 variable by a small amount (perhaps 1/100 of your graph, though you will need to adjust this to taste), run it through the function again and save those variables(x2 and y2) as your second point. Draw a line between these two points. Save your x2,y2 variables as x1, y1 and increment your x value again to find the third point, so on and so forth. This is not a "true" curve as it is really just a series of lines which approximate the function, a calculus approach if you will.
So:
x1 = x; // where x is some point on the x axis which you would like to start graphing at.
y1 = sin(x);
x2 = x1 + increment;
y2 = sin(x2);
//Draw a line here
x1 = x2;
y1 = y2;
//return to top, this code would obviously be in a loop in which uses increment as it's own increment with the initial value being equal to the amount you want to increment each time(let's say....5) and the "next" statement being increment = increment + 5.
There is also a GraphCanvas class which I am unfamiliar with which appears to take those same points and draw the curve between them, though I am unsure what sort of transform is being used to draw the curve and how accurate that is. Here is the Class:
http://www.java2s.com/Code/Java/Swing-Components/GraphCanvas.htm
Im trying to get into some basic JavaFX game development and I'm getting confused with some circle maths.
I have a circle at (x:250, y:250) with a radius of 50.
My objective is to make a smaller circle to be placed on the circumference of the above circle based on the position of the mouse.
Where Im getting confused is with the coordinate space and the Trig behind it all.
My issues come from the fact that the X/Y space on the screen is not centered at 0,0. But the top left of the screen is 0,0 and the bottom right is 500,500.
My calculations are:
var xpos:Number = mouseEvent.getX();
var ypos:Number = mouseEvent.getY();
var center_pos_x:Number = 250;
var center_pos_y:Number = 250;
var length = ypos - center_pos_y;
var height = xpos - center_pos_x;
var angle_deg = Math.toDegrees(Math.atan(height / length));
var angle_rad = Math.toRadians(angle_deg);
var radius = 50;
moving_circ_xpos = (radius * Math.cos(angle_rad)) + center_pos_x;
moving_circ_ypos = (radius * Math.sin(angle_rad)) + center_pos_y;
I made the app print out the angle (angle_deg) that I have calculated when I move the mouse and my output is below:
When the mouse is (in degrees moving anti-clockwise):
directly above the circle and horizontally inline with the center, the angle is -0
to the left and vertically centered, the angle is -90
directly below the circle and horizontally inline with the center, the angle is 0
to the right and vertically centered, the angle is 90
So, what can I do to make it 0, 90, 180, 270??
I know it must be something small, but I just cant think of what it is...
Thanks for any help
(and no, this is not an assignment)
atan(height/length) is not enough to get the angle. You need to compensate for each quadrant, as well as the possibility of "division-by-zero". Most programming language libraries supply a method called atan2 which take two arguments; y and x. This method does this calculation for you.
More information on Wikipedia: atan2
You can get away without calculating the angle. Instead, use the center of your circle (250,250) and the position of the mouse (xpos,ypos) to define a line. The line intersects your circle when its length is equal to the radius of your circle:
// Calculate distance from center to mouse.
xlen = xpos - x_center_pos;
ylen = ypos - y_center_pos;
line_len = sqrt(xlen*xlen + ylen*ylen); // Pythagoras: x^2 + y^2 = distance^2
// Find the intersection with the circle.
moving_circ_xpos = x_center_pos + (xlen * radius / line_len);
moving_circ_ypos = y_center_pos + (ylen * radius / line_len);
Just verify that the mouse isn't at the center of your circle, or the line_len will be zero and the mouse will be sucked into a black hole.
There's a great book called "Graphics Gems" that can help with this kind of problem. It is a cookbook of algorithms and source code (in C I think), and allows you to quickly solve a problem using tested functionality. I would totally recommend getting your hands on it - it saved me big time when I quickly needed to add code to do fairly complex operations with normals to surfaces, and collision detections.