Good evening.
I'm trying to rotate a line on a canvas using setRotation -method and it works perfectly, unless you want to draw another shape on the same canvas. After using Canvas's concat -method, the entire canvas will be rotated, let's say, by 30 degrees counterclockwise/clockwise. And this is the problem. I would like to rotate only the line and I don't want to rotate any other shapes on this canvas or the entire canvas. I found out that the bitmap could be drawn with matrices, but it looks cumbersome and clumsy. Also, there was a suggestion to setup a new matrix for the Canvas, in fact, this proposition works neither.
So, the question sounds simple enough, how could the only one shape on the canvas be rotated without using OpenGl and affecting on other shapes on the canvas?
Thank you for your answers in advance!
Here is the code with comments and other stuff:
#Override
public void onDraw(Canvas canvas)
{
int startX, startY, stopX, stopY;
startY = stopY = 100;
startX = 100;
stopX = 200;
this.paint = new Paint();
//this.path = new Path();
this.matrix = canvas.getMatrix();
this.paint.setColor(Color.BLUE);
this.paint.setStrokeWidth(4);
this.matrix.setRotate(180, startX, startY);
canvas.concat(this.matrix);
/*this.matrix.setTranslate(startX, 0);
canvas.concat(this.matrix);*/
canvas.drawLine(startX, startY, stopX, stopY, this.paint);
canvas.setMatrix(new Matrix());
//canvas.drawCircle(200, 200, 50, paint);
}
You could try Canvas.save() and Canvas.restore() for this. They supposedly put current matrix into stack and you can return back to previous one once done with the modified matrix.
this.matrix.setRotate(180, startX, startY);
canvas.save();
canvas.concat(this.matrix);
canvas.drawLine(startX, startY, stopX, stopY, this.paint);
canvas.restore();
Mozoboy proposed the idea of using the linear algebra. Here is a new code of the onDraw(Canvas canvas) method with using linear algebra and staying in the scopes of the Android APIs:
Matrix m = new Matrix(); //Declaring a new matrix
float[] vecs = {7, 3}; //Declaring an end-point of the line
/*Declaring the initial values of the matrix
according to the theory of the 3
dimensional chicken in 2D space
There is also 4D chicken in 3D space*/
float[] initial = {1, 0, 0, 0, 1, 0, 0, 0, 1};
m.setValues(initial);
float[] tmp = new float[9]; //Debug array of floats
m.setRotate(90, 4.0f, 3.0f); //Rotating by 90 degrees around the (4, 3) point
/*Mapping our vector to the matrix.
Similar to the multiplication of two
matrices 3x3 by 1x3.
In our case they are (matrix m after rotating) multiplied by
(7)
(3)
(1) according to the theory*/
m.mapPoints(vecs);
for(float n : vecs)
{
Log.d("VECS", "" + n); //Some debug info
}
m.getValues(tmp);
for(float n : tmp)
{
Log.d("TMP", "" + n); //also debug info
}
As a result of this algorithm, we have a new coordinates of the line's end point (4, 6).
Related
I made a simple program that simulates cells in a grid with their own color and displays them as 4x4 pixels, at first i made it using java AWT and the performance was ok but i was curious about how LibGDX perform and it was about 5 times slower, am i doing something wrong or is LibGDX just that much slower? here is my rendering loop:
#Override
public void create () {
cam = new OrthographicCamera(width, height);
Gdx.graphics.setWindowedMode(width * scale, height * scale);
renderer = new ShapeRenderer();
inputs = new Inputs();
world = new World(width, height, 8);
Gdx.input.setInputProcessor(inputs);
}
Pixel thisPixel;
#Override
public void render () {
Gdx.gl.glClearColor(0, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
renderer.setProjectionMatrix(cam.combined);
renderer.begin(ShapeRenderer.ShapeType.Filled);
inputs.update();
for (int x = 0; x < width; x ++)
for (int y = 0; y < height; y ++) {
thisPixel = world.getPixel(x, y);
renderer.setColor(world.materials.colors[thisPixel.colorId]);
renderer.rect(x - width / 2 - 1, y - height / 2 - 1, 1, 1);
}
renderer.end();
System.out.println(Gdx.graphics.getFramesPerSecond());
}
AWT use canvas to draw which is a 2D surface. LibGDX use openGL which is for 3D rendering and you have to project it onto a 2D surface (renderer.setProjectionMatrix(cam.combined);)
Drawing rects and scaling are quite costly operations, so if you can find an other way to achieve what you want it might speed things up.
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 :)
I'm writing a game for Android using Java and OpenGL. I can render everything perfectly to screen, but when I try to check whether two objects collide or not, my algorithm detects a collision before it occurs on the screen.
Here's how I test for collision:
for(int i=0; i<enemies.size(); i++) {
float enemyRadius = enemies.elementAt(i).worldSpaceBoundingSphereRadius();
float[] enemyPosition = enemies.elementAt(i).getWorldSpaceCoordinates();
for(int j=0; j<qubieBullets.size(); j++) {
float bulletRadius = bullets.elementAt(j).worldSpaceBoundingSphereRadius();
float[] bulletPosition = bullets.elementAt(j).getWorldSpaceCoordinates();
float[] distanceVector = Vector3f.subtract(enemyPosition, bulletPosition);
float distance = Vector3f.length(distanceVector);
if(distance < (enemyRadius + bulletRadius)) {
enemies.remove(i);
qubieBullets.remove(j);
i--;
j--;
// Reset enemy position
}
}
}
When the enemy cube (represented by a sphere for collision detection) closes in on the player, the player shoots a bullet (also a cube represented by a sphere) toward the enemy. My expectations are that the enemy gets reset when the bullet hits him on screen, but it happens way earlier than that.
The methods for calculation world space position and radius:
public float[] getWorldSpaceCoordinates() {
float[] modelSpaceCenter = {0.0f, 0.0f, 0.0f, 1.0f};
float[] worldSpaceCenter = new float[4];
Matrix.multiplyMV(worldSpaceCenter, 0, getModelMatrix(), 0, modelSpaceCenter, 0);
return new float[] {worldSpaceCenter[0]/worldSpaceCenter[3], worldSpaceCenter[1]/worldSpaceCenter[3], worldSpaceCenter[2]/worldSpaceCenter[3]};
}
public float worldSpaceBoundingSphereRadius() {
float[] arbitraryVertex = new float[] {1.0f, 1.0f, 1.0f, 1.0f};
float[] worldSpaceVector = new float[4];
Matrix.multiplyMV(worldSpaceVector, 0, getModelMatrix(), 0, arbitraryVertex, 0);
float[] xyz = new float[] {worldSpaceVector[0]/worldSpaceVector[3], worldSpaceVector[1]/worldSpaceVector[3], worldSpaceVector[2]/worldSpaceVector[3]};
return Vector3f.length(xyz);
}
Is it my code or math that's wrong? I can't think of anything more to try, and would be helpful if someone could point me in the right direction.
Your worldSpaceBoundingSphereRadius() is most likely the culprit. arbitraryVertex is a Vector of (1,1,1) so your math will only work if the cube model has edges of length 2 * sqrt(1/3). What you want to do is find the exact length of your cube's model's edge, use the formula from my comment (rad = sqrt( 3 * (x/2) * (x/2) )) and use that radius for your arbitraryVertex (rad,rad,rad,1).
Also, your dividing the results of your multiplication by the homogenous coordinate (worldSpaceVector[0]/worldSpaceVector[3]). With a proper rotation, translation, or scale, the homogenous coordinate should always be exactly 1 (if it started as one). If it isn't, you might have a projection matrix in there or something else that isn't a basic transformation.
EDIT:
Since you're using worldSpaceBoundingSphereRadius() to get only the radius, you want only the scaling component of getModelMatrix(). If that returns scaling and translation, this translation will apply to your radius and make it much larger than it actually is.
There is something I am missing inhere so I hope you can share some light on me.
I am drawing some text inside canvas. For this I have a class Word
public class Word {
private int x;
private int y;
private String text;
}
The app allows the user to rotate the text, and I handle the rotation withing onDraw
protected void onDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(angle, centerX, centerY)
...
canvas.drawText(word.getText(), word.getX(), word.getY())
....
canvas.restore();
}
The problem I get is when the user drags the canvas and there is a rotation set. When angle=0 the movement is going as expected.
#Override
public boolean onTouchEvent(MotionEvent event) {
case MotionEvent.ACTION_DOWN:
initialX = (int) event.getX();
initialY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int currentX = (int) event.getX();
int currentY = (int) event.getY();
int xMovement = currentX - initialX;
int yMovement = currentY - initialY;
dragWords(xMovement, yMovement);
.....
and on dragWords for each word I do:
private void dragText(int xMovement, int yMovement){
for (Word word : words) {
word.setX(word.getX() + xMovement);
word.setY(word.getY() + yMovement);
}
invalidate();
}
When rotation angle is 0, moving up/down/left/right makes the words move by the same distance. As angle gets bigger, the words start to move in different dirrections, for instance at 60, it is starting to go diagonally up, when 180 it only moves up/down and not left/right.
I think I need to calculate some sort of a difference based on angle and add it to xMovement/yMovement... but how should I do this ?
LE: Here is an image on how it behaves:
The blue lines is how the text is moving on drag while the orange is the finger dragging on the screen. When angle is 0 it works quite well, when angle increases, it starts to move diagonally on left/right, while when angle is even bigger, it only moves up and down and does not respond to left/right
If I understand correctly, the issue is that Canvas.rotate() does not only rotate the text direction, but rather the whole canvas. Therefore, the x-y coordinates of the words are also rotated from the specified pivot point.
In order to match the dragging movement, you can use a Matrix for this, more specifically the inverse matrix of the one you're using to rotate the canvas. It will be used to convert the x-y coordinates of the words to their original, pre-rotate locations.
For example, calculate this once, and update it whenever angle, centerX, or centerY changes.
// rotMatrix is the same operation applied on the canvas.
Matrix rotMatrix = new Matrix();
rotMatrix.postRotate(mAngle, centerX, centerY);
// Invert it to convert x, y to their transformed positions.
Matrix matrix = new Matrix();
rotMatrix.invert(matrix);
Then, when drawing each word:
int wordX = ...
int wordY = ...
String text = ...
float[] coords = new float[] { wordX, wordY };
matrix.mapPoints(coords);
canvas.drawText(text, coords[0], coords[1], paint);
In the ellipses part in the following code:
dragWords(xMovement, yMovement);
..... <<<--------------------- I hope you are updating initialX and initialY
initialX = currentX;
initialY = currentY;
Otherwise, your x and y values will not correspond correctly with the amount of distance moved during the touch gesture.
As user matiash indicated, you should use Matrix#mapPoints(float[]) to transform your x and y values. Declare and initialize a Matrix:
Matrix correctionMatrix;
// Your view's constructor
public MyView() {
....
correctionMatrix = new Matrix();
}
Here's how your onDraw(Canvas) should look like:
#Override
protected void onDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(angle, centerX, centerY);
...
// Neutralize the rotation
correctionMatrix.setRotate(-angle, centerX, centerY);
// Initialize a float array that holds the original coordinates
float[] src = {word.getX(), word.getY()};
// Load transformed values into `src` array
correctionMatrix.mapPoints(src);
// `src[0]` and `src[1]` hold the transformed `X` and `Y` coordinates
canvas.drawText(word.text, src[0], src[1], somePaint);
....
canvas.restore();
}
This should give you the desired results - movement in the X and Y axis irrespective of canvas rotation.
You can obviously move the call to setRotate(float, float, float) to a better place. You only need to call it once after changing the angle value.
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