I'm facing a task where I have to draw polylines using polygons.
As an input parameters I have an array of points and thickness value. See picture below.
I have points that form black polyline and thickness e.g. 10px. Now I need to calculate points and construct a blue polyline to form a polygon and then render it.
There are some articles related to this:
Drawing Polylines by Tessellation
An algorithm for polylines outline construction
EFFICIENT RENDERING OF THICK POLYLINES
But I find them a bit complicated and difficult to understand.
Aren't there any existing libraries or more simple algorithms to implement this. No rounded joints are required. I'm using java and libGDX.
The algorithm is as follows:
for each line:
find the parallel line upwards:
find the perpendicular: has a slope m2 in approximate
check which side is right (compare angles)
find the two points of the parallel line by solving a small equation problem (A, B, C)
if this line is the first one keep it (l1)
else find the intersection with the previous line (l1, l2): this will give the next articulation point
The yellow line is the one you want; the red line is the general parallel line.
The articulation points are in green. You can print this bufferedimage in some component.
Notes: the width of the polygon cannot be fixed as you realize because at articulation points the distance will be larger. What is guaranteed is that the distance between line segments is constant.
int[] approximate(int[] p, int[] p2, double dr, int l) { // l is the distance, dr is 0 for the beginning of the segment and 1 for the end
double d=Math.sqrt(Math.pow(p[0]-p2[0], 2)+Math.pow(p[1]-p2[1], 2));
double ix=p[0]+dr*(p2[0]-p[0]), iy=p[1]+dr*(p2[1]-p[1]);
double x1=0, x2=0, y1=0, y2=0;
if(p2[0]==p[0]) {
x1=ix+l; x2=ix-l; y1=iy; y2=iy;
}
else {
double m=1.0*(p2[1]-p[1])/(p2[0]-p[0]);
if(Math.abs(m)==0) {
x1=ix; x2=ix; y1=iy+l; y2=iy-l;
}
else {
double m2=-1/m;
double c=iy-m2*ix;
double A=1+m2*m2, B=-2*(ix-m2*c+m2*iy), C=ix*ix+iy*iy+c*c-2*c*iy-l*l;
x1=(-B+Math.sqrt(B*B-4*A*C))/(2*A); x2=(-B-Math.sqrt(B*B-4*A*C))/(2*A); y1=m2*x1+c; y2=m2*x2+c;
}
}
int[] cp1={p2[0]-p[0], p2[1]-p[1]}, cp2={(int)x1-p[0], (int)y1-p[1]}, xy=new int[2];
int cpp=compareAngles(cp1, cp2);
if(cpp>0) { xy[0]=(int)x1; xy[1]=(int)y1; } else { xy[0]=(int)x2; xy[1]=(int)y2; }
return xy;
}
void poly() {
int[][] p={{100, 400}, {110, 440}, {250, 300}, {350, 400}, {300, 310}};
BufferedImage bim=new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
Graphics2D g=(Graphics2D)bim.getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, 500, 500);
g.setStroke(new BasicStroke(5f));
g.setColor(Color.black);
Line2D.Double l1=new Line2D.Double(), l2=new Line2D.Double();
int[] currentp=new int[2], lastp=new int[2];
for(int i=0; i<p.length-1; i++) {
g.setColor(Color.black);
g.drawLine(p[i][0], p[i][1], p[i+1][0], p[i+1][1]);
int[] p1=approximate(p[i], p[i+1], 0, 10), p2=approximate(p[i], p[i+1], 1, 10);
g.setColor(Color.red);
g.drawLine(p1[0], p1[1], p2[0], p2[1]);
if(i==0) { l1=new Line2D.Double(p1[0], p1[1], p2[0], p2[1]); currentp[0]=p1[0]; currentp[1]=p1[1]; }
else {
l2=new Line2D.Double(p1[0], p1[1], p2[0], p2[1]);
int[] pi=intersectionPoint(l1, l2);
g.setColor(Color.green);
g.fillOval(pi[0], pi[1], 5, 5);
g.setColor(Color.yellow);
g.drawLine(currentp[0], currentp[1], pi[0], pi[1]);
currentp[0]=pi[0]; currentp[1]=pi[1];
l1.setLine(l2);
}
if(i==p.length-2) { lastp[0]=p2[0]; lastp[1]=p2[1]; }
}
g.setColor(Color.yellow);
g.drawLine(currentp[0], currentp[1], lastp[0], lastp[1]);
}
public int[] intersectionPoint(Line2D.Double l1, Line2D.Double l2) {
return intersectionPoint((int)l1.getX1(), (int)l1.getY1(), (int)l1.getX2(), (int)l1.getY2(), (int)l2.getX1(), (int)l2.getY1(), (int)l2.getX2(), (int)l2.getY2());
}
public int[] intersectionPoint(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
int[] xy={(int)(1.0*((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))),
(int)(1.0*((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)))};
return xy;
}
public int compareAngles(int[] a, int[] b) {
int cp=a[0]*b[1]-a[1]*b[0];
return -cp;
}
I'm not really sure why you want to implement some advanced graphics alghoritms in framework which first place duty is to render things easily :)
Libgdx has built-in ShapeRenderer that allows you to draw simple shapes. All you have to do is to calculate new vertices basing on thickness and pass them to the ShapeRenderer to draw circles and lines that connects these circles.
To make it super-easy you can even use
rectLine(float x1, float y1, float x2, float y2, float width)
method that allows you to draw line of given thickness. So only what you need to do is to iterate over points and draw all lines like in this pseudo code:
for point in points:
if thereIsANextPoint():
next = getNextPoint()
sr.rectLine(point.x, point.y, next.x, next .y, thickness)
The nice description of how to use ShapeRenderer is included in reference I've attached above
I guess that there can be a little problem with joinings between points (because of different angles for example) I think that rendering circles above this joins will be good workarround :)
Related
I like to have maximum control over the screen, so I have to control every pixel, and that has some pros and cons. one con is that I don't really have the help from any built-in functions. so I have no idea how to draw a line.
I've tried to make a function to handle line drawing but I just can't get it to work!
here's the code I used to draw the line
int startX;
int startY;
int deltaX = x1/x2;
int deltaY = y1/y2;
float deltaPixl = deltaX/deltaY;
for(int i=0;i<deltaY;i=i+1){
if(x1>x2){ startX = x2;}else{ startX=x1;}
if(y1>y2){ startY = y2;}else{ startY=y1;}
pixl(startX+i,round(startY+(deltaPixl*i)),0);
}
it uses a function called pixl so that it easily draw a pixel to the pixel array,
just to clarify why there's a function called pixl in the code.
and when I try to use this code, it doesn't crash, like processing usually does when it has an error!
it just doesn't work, instead just doing nothing!
I'd like some help on this subject, please.
You could get away with simply using PGraphics.
The idea is once you have a PGraphics instance you use dot notation to access the drawing functions used to (as long as they're called between .beginDraw() and .endDraw()).
Using noSmooth() you can get it looking pixel perfect.
Here's a basic sketch to illustrate the idea:
// disable anti-aliasing
noSmooth();
// create a PGraphics layer
PGraphics layer = createGraphics(25, 25);
// render a line
layer.beginDraw();
layer.line(0, 24, 24, 0);
layer.endDraw();
// render the line at 100%
image(layer, 0, 0);
// render the line scaled up
image(layer, 0, 0, width, height);
This should do for most cases. (It's only trickier cases with very small values and transparency that might give you headaches)
If for some reason you need a lot more control, you can you always implement your own method of rasterising. Once place you can probably start with is Bresenham's line algorithm
Regarding your code there are a few things that could go wrong:
float deltaPixl = deltaX/deltaY;: if deltaY is zero you'll run into an exception
you're doing integer division for deltaX and deltaY (potentially making it likely to get 0 for either of the values)
you should try a println() statement before the for loop with the start/end values to get a feel if that loop will actually execute or not. Additionally, within the for loop you can println(i) to see if you get the value you expect.
Overall I recommend checking Kevin Workman's How to Debug guide.
Additionally you could use lerp() to calculate linearly interpolated position between the line's start and end points. Pass each coordinate and a normalized (between 0.0, 1.0) value, where 0.0 = at the start point, 1.0 = at the end point and anything in between is on the line (e.g. 0.5 = 50% along the line).
Here's a basic example:
void drawLinePoints(int x1, int y1, int x2, int y2, int numberOfPoints){
// for each point
for(int i = 0; i < numberOfPoints; i++){
// map the counter to a normalized (0.0 to 1.0) value for lerp
// 0.0 = 0 % along the line, 0.5 = 50% along the line, 1.0 = 100% along the line
float t = map(i, 0, numberOfPoints, 0.0, 1.0);
// linearly interpolate between the start / end points (and snap to whole pixels (casting to integer type))
int x = (int)lerp(x1, x2, t);
int y = (int)lerp(y1, y2, t);
// render the point
point(x, y);
}
}
void setup(){
// render points are large squares
strokeWeight(6);
strokeCap(PROJECT);
}
void draw(){
// clear frame
background(255);
// calculate distance
float distance = dist(10, 10, mouseX, mouseY);
// map distance the number of points to illustrate interpolation (more points = continuous line)
int numPoints = (int)distance / 8;
// render points along the line
drawLinePoints(10, 10, mouseX, mouseY, numPoints);
}
For the sake of completeness here's the above snippet using the pixels[] instead:
void drawLinePoints(int x1, int y1, int x2, int y2, int numberOfPoints){
// for each point
for(int i = 0; i < numberOfPoints; i++){
// map the counter to a normalized (0.0 to 1.0) value for lerp
// 0.0 = 0 % along the line, 0.5 = 50% along the line, 1.0 = 100% along the line
float t = map(i, 0, numberOfPoints, 0.0, 1.0);
// linearly interpolate between the start / end points (and snap to whole pixels (casting to integer type))
int x = (int)lerp(x1, x2, t);
int y = (int)lerp(y1, y2, t);
// convert the x, y coordinate to pixels array index and render the point in black
pixels[x + (y * width)] = color(0);
}
}
void setup(){
noSmooth();
}
void draw(){
// clear frame
loadPixels();
java.util.Arrays.fill(pixels, color(255));
// calculate distance
float distance = dist(10, 10, mouseX, mouseY);
// map distance the number of points to illustrate interpolation (more points = continuous line)
int numPoints = (int)distance;
// render points along the line
drawLinePoints(10, 10, mouseX, mouseY, numPoints);
// update pixels
updatePixels();
}
I'm a bit late but I found a very simple method for line drawing to a pixel array on this website.
Here is a simple implementation I made in Monogame (btw sorry its not using processing - I have never used it):
public void drawLine(int x1, int y1, int x2, int y2)
{
//this will store the colour data of the canvas pixels
Color[] canvasData = new Color[canvas.Width * canvas.Height];
//store the pixel data of the canvas in canvasData
canvas.GetData<Color>(canvasData);
//drawing line starts here
int dx = x2 - x1;
int dy = y2 - y1;
for (int x = x1; x < x2; x++)
{
int y = y1 + dy * (x - x1) / dx;
//[y*canvas.Width+x] converts the 2d array index to a 1d array index
canvasData[y * canvas.Width + x] = Color.Black;
}
//line drawing ended
//setting the canvas' pixels to the modified pixels with the line
canvas.SetData<Color>(canvasData);
}
I want to make a visual representation of the Pythagoras tree using Java, the code outputs a PNG fixed image.
I started by defining Vector class which starting from two vector components (x,y) can rotate the vector, scale it or add it to another vector.
public class Vector {
public double x;
public double y;
public Vector(double x, double y) {
this.x = x;
this.y = y;
}
public Vector rotated(double alpha) {
double x1 = Math.cos(alpha) * x - Math.sin(alpha) * y;
double y1 = Math.sin(alpha) * x + Math.cos(alpha) * y;
Vector vRotated = new Vector(x1, y1);
return vRotated;
}
public Vector scaled(double s) {
double x1 = x * s;
double y1 = y * s;
Vector vScaled = new Vector(x1, y1);
return vScaled;
}
public Vector added(Vector v) {
double x1 = this.x+v.x;
double y1 = this.y+v.y;
Vector vAdded = new Vector(x1,y1);
return vAdded;
}
}
I have also writen the method for creating the initial image and background and saving it to the desired path
public static void createPythagorasTreeImage(int startSize) throws IOException {
// Creation of the image object
int height = 5 * startSize;
int width = 8 * startSize;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Create a Graphics2D object from the image and set a white background
Graphics2D g = image.createGraphics();
g.setColor(new Color(255, 255, 255));
g.fillRect(0, 0, width, height);
// Initial position and orientation of the first segment
Vector startPos = new Vector(width / 2, startSize);
Vector up = new Vector(0, 1);
// Start the recursion.
drawSegment(g, startPos, up, startSize, height);
// Save the image as PNG
String OS = System.getProperty("os.name").toLowerCase(); // different for win and unix
String filePath = System.getProperty("user.dir") + (OS.indexOf("win") >= 0 ? "\\" : "/") + "pythagorasTree.png";
System.out.println("Writing pythagoras-tree image to: " + filePath);
ImageIO.write(image, "png", new File(filePath));
}
I have read on wikipedia on how to the tree works, and want to now implement the algorithm.
What I need help with is implementing these two methods using Graphics2D (which I'm not very familiar with):
public static void drawRotatedRect(Graphics2D g, Vector pos, Vector up, int a, int height) {
}
This method should Draw a square using Graphics2D (maybe using g.fillPolygon()?), at position pos, up the vector that indicates the rotation of the square by indicating which direction is up for the square, a is the side of the square and height is the height of the drawing space.
public static void drawSegment(Graphics2D g, Vector pos, Vector up, int a, int height) {
}
This method should draw the first square using the previous method, than compute the positions and rotations of the two new squares and draw them, repeat this recursively until a square has a very small side length (2px).
This is my understanding for the Pythagoras tree, I managed to write the majority of the code and it seems that the idea is correct, only if I get the two missing methods to work.
You can work with the Graphics2D context by drawing a Path2D with floating point (or double) precision. I reccoment this, since you will notice that using int precision might give you weird effects.
To draw a path, do:
Path2D.Double rectangle = new Path2D.Double();
rectangle.moveTo(0, 0);
// ... basically draw the four points of the rectangle here.
rectangle.closePath();
g.setColor(yourColorOfChoice);
g.fill(rectangle);
Notice that you need to draw the rectangular shapes manually, since they need ot be rotated, and Graphics2D does not do well with rotations. You could try using inherent rotations, but you will pixelate your context, and you won't like it.
I am very much looking forward to your results. Could you paste the final image into your question, once you are done :)?
i'm stuck on following problem;
I have a rectangle (50x40 px lets say, position : x1,y1) and a cirle (radius 30, positin x2,y2). Now I want to draw an arrow between them
void drawArrow(Graphics g1, int x1, int y1, int x2, int y2,) {
//x1 and y1 are coordinates of circle or rectangle
//x2 and y2 are coordinates of circle or rectangle, to this point is directed the arrow
Graphics2D g = (Graphics2D) g1.create();
double dx=x2-x1;
double dy=y2-y1;
double angle = Math.atan2(dy, dx);
int len = (int) Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.concatenate(AffineTransform.getRotateInstance(angle));
g.transform(at);
g.drawLine(0,0,len,0);
g.fillPolygon(new int[] {len, len-ARR_SIZE, len-ARR_SIZE, len},
new int[] {0, -ARR_SIZE, ARR_SIZE, 0}, 4);
}
This Code obviously connects only the specific points of rect and circle ( on this picture i connected the points in the middle http://imageshack.us/photo/my-images/341/arrk.jpg/ ). Do you have any idea how to achieve stg like this? (http://imageshack.us/photo/my-images/833/arr2u.jpg/ ) ... my idea was to shorten the length and calculate the new coordinates, but i'm bit struggling with it.
// I call this function this way:
drawArrow(g,temp.x+radius/2,temp.y+radius/2,temp2.x+width/2,temp2.y+height/2);
Easiest way is to set the clipping. If you add your circle and your rect to the clipping, it won't draw on it.
It doesn't solve the problem or drawing the arrow though. To solve this problem, you need to use Shape.getBounds(), figure out the bounds for the rectangle, then calculate the angle to your circle and use trigonometry to find the right spot on the rectangle
im trying to draw an arc - just a simple looking arc from point (x1,y1) to point (x2,y2)
how do i do that?
i been using the so complex and not freindly to user method called drawArc on Graphics class. no luck yet tho.
thats what i tried:
void drawArc(Graphics2D g, int x1, int y1, int x2, int y2) {
AffineTransform prev = g.getTransform();
double dx = x2 - x1, dy = y2 - y1;
double angle = Math.atan2(dy, dx);
int len = (int) Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.rotate(angle);
g.transform(at);
g.drawArc(len/2, len/2, len ,len/2, 0, 60);
g.setTransform(prev);
}
thanks ahead.
graphics.drawLine(x1,y1,x2,y2) is the simplest possible arc that you can draw with these information.
Probably it is not what you want. If you want something more ... curvy you need to define somehow how curvy it is, in what direction. The drawArc method requires you to calculate an ellipse that touches both points. The arc is the segment of the circle between those points. There is an infinite number of possible ellipses. (The drawLine example assumes an infinite ellipse.) But this requires more information (what ellipse to chose) and some calculation.
If you want to draw curves between two points and control points (what you probably want) you need to look into QuadCurve2D or CubicCurve2D and drawShape. You can find sample code here.
I'm using Java AWT to draw lines on a panel (Line2D and Graphics2D.drawLine()) and I'm wondering how I can draw a line with tick marks, similar to:
|----|----|----|----|----|
I know the positions I'd like to draw the ticks at in advance.
The lines could be in any position, so the ticks must be drawn at an angle releative to the line itself.
My basic geometry & ability to apply it in Java is failing me. :)
I suggest you
implement a ruler-drawing-method that draws a simple horizontal ruler from left to right
Figure out the desired angle using Math.atan2.
Apply an AffineTransform with translation and rotation before invoking the ruler-drawing-method.
Here is a complete test-program. (The Graphics.create method is used to create a copy of the original graphics object, so we don't mess up the original transform.)
import java.awt.*;
public class RulerExample {
public static void main(String args[]) {
JFrame f = new JFrame();
f.add(new JComponent() {
private final double TICK_DIST = 20;
void drawRuler(Graphics g1, int x1, int y1, int x2, int y2) {
Graphics2D g = (Graphics2D) g1.create();
double dx = x2 - x1, dy = y2 - y1;
double len = Math.sqrt(dx*dx + dy*dy);
AffineTransform at = AffineTransform.getTranslateInstance(x1, y1);
at.concatenate(AffineTransform.getRotateInstance(Math.atan2(dy, dx)));
g.transform(at);
// Draw horizontal ruler starting in (0, 0)
g.drawLine(0, 0, (int) len, 0);
for (double i = 0; i < len; i += TICK_DIST)
g.drawLine((int) i, -3, (int) i, 3);
}
public void paintComponent(Graphics g) {
drawRuler(g, 10, 30, 300, 150);
drawRuler(g, 300, 150, 100, 100);
drawRuler(g, 100, 100, 120, 350);
drawRuler(g, 50, 350, 350, 50);
}
});
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(400, 400);
f.setVisible(true);
}
}
Note, that you could just as easily draw numbers above the ticks. The drawString-calls would go through the same transformation and get nicely "tilted" along the line.
Things that need noting:
A perpendicular line has a slope of -1/oldslope.
In order to support lines in any direction, you need to do it parametrically
Thus, you have dy and dx across the original line, which means that newdx=dy; newdy=-1*dx.
If you have it such that <dx, dy> is a unit vector (sqrt(dx*dx+dy+dy)==1, or dx==cos(theta); dy=sin(theta) for some theta), you then just need to know how far apart you want the tick marks.
sx, sy are your start x and y
length is the length of the line
seglength is the length of the dashes
dx, dy is the slopes of the original line
newdx, newdy are the (calculated above) slopes of the cross lines
Thus,
Draw a line from <sx,sy> (start x,y) to <sx+dx*length,sy+dy*length>
Draw a set of lines (for(i=0;i<=length;i+=interval) from <sx+dx*i-newdx*seglength/2,sy+dy*i-newdy*seglength/2> to <sx+dx*i+newdx*seglength/2,sy+dy*i+newdy*seglength/2>
I hope you know matrix multiplication. In order to rotate a line you need to multiple it by rotation matrix. (I coudln't draw a proper matrix but assume both line are not separated)
|x'| = |cos(an) -sin(an)| |x|
|y`| = |sin(an) cos(an)| |y|
The old points are x,y and the new is x',y'. Let us illustrate by an example, lets say you have a vertical line from (0,0) to (0,1), now you want to rotate it by 90 degrees. (0,0) will remain zero so lets just see what happens to (0,1)
|x'| = |cos(90) -sin(90)| |0|
|y`| = |sin(90) cos(90)| |1|
==
|1 0| |0|
|0 1| |1|
==
| 1*0 + 0*1|
| 0*0 + 1*1|
== |0|
|1|
you get to horizontal line (0,0),(0,1) like you would expect.
Hope it helps,
Roni