I need some help with matrix operations. What I'm trying to achieve is:
Scale down
Move to a specific position
Rotate by some degree (in the center of the bitmap)
My code currently looks like this:
Matrix matrix = new Matrix();
matrix.preRotate(mShip.getRotation(), mShip.getX() + mShip.getCurrentBitmap().getWidth()/2f, mShip.getY() + mShip.getCurrentBitmap().getHeight()/2f);
matrix.setScale((1.0f * mShip.getWidth() / mShip.getCurrentBitmap().getWidth()), (1.0f * mShip.getHeight() / mShip.getCurrentBitmap().getHeight()));
matrix.postTranslate(mShip.getX(), mShip.getY());
mCanvas.drawBitmap(mShip.getCurrentBitmap(), matrix, mBasicPaint);
But the rotation has the wrong center, and I can't figure out how to solve this - I've already looked around on SO but did only find similar problems, no solutions to this.
I think that I might have to apply one of the operations to another one's values as they are executed in a sequence but I cant figure out how to.
Try this code:
Matrix matrix = new Matrix();
matrix.setTranslate(-mShip.getCurrentBitmap().getWidth()/2f, -mShip.getCurrentBitmap().getHeight()/2f);
matrix.postRotate(mShip.getRotation());
matrix.postTranslate(mShip.getX(), mShip.getY());
matrix.postScale((1.0f * mShip.getWidth() / mShip.getCurrentBitmap().getWidth()), (1.0f * mShip.getHeight() / mShip.getCurrentBitmap().getHeight()), mShip.getX(), mShip.getY());
Related
By default, the modelInstance is rotated by its centre (0,0,0), I want it to rotate by (0,2,2). I know that in other game engines, there is method like model.setRotationPivot(float), is there any similar method in libgdx?
// how to set rotation pivot?
modelInstance.transform.set(position, rotation, scale);
Thanks!
Late answer:
As I know there is no method to set the pivot.
I use a workaround for this.
Vector3 vec3 = new Vector3(0, 2, 2);
vec3.rotate(Vector3.Y, rotation);
modelInstance.transform.setToTranslation(vec3);
this.transform.rotate(Vector3.Y, rotation);
Let's say I start with an Bitmap that's 1000px x 1000px
I load it into a SurfaceView resident Canvas that displayed at some arbitrary (and depending on the device different) dimensions.
I can get those dimensions at runtime and measure the scale between them and the original (if I need that information at the end).
Then I allow the user to pinch/zoom/translate the image around the displayed canvas. All the while I have a Matrix which keeps track of, and is used to re-draw the image in its displayed screen region.
Subsequently this Matrix's values all apply to the scaled space (and not the original 1000x1000 graphic).
So far so good - I have all this working.
However, when all is said and done, I'd like to apply this Matrix to the original Bitmap and save it out. However, I'm at a loss as to how to modify all its internal values to apply it back to the unscaled original (1000x1000) size.
Curious if there's some auto-magical way to translate these or if I have to somehow apply each value based on the scale between the two sizes back to a new Matrix.
To invert a matrix
matrix{ a,b,c,d,e,f } and the inverse is matrix { ia, ib, ic, id, ie, if }
var cross = a * d - b * c;
ia = d / cross;
ib = -b / cross;
ic = -c / cross;
id = a / cross;
ie = (c * f - d * e) / cross;
if = -(a * f - b * e) / cross;
Reverse the transform, the original from image coordinates to screen coordinates and the inverse matrix transforms screen coordinates to image coordinates.
If you have a transform on the screen and you want to know where on the image the top left of the screen is. Get the inverse transform and apply it to the screen coordinate (0,0) top left.
scrX = ?
scrY = ?
imageX = scrX * ia + scrY * ic + ie;
imageY = scrX * ib + scrY * id + if;
The game I'm developing is progressing, more and more elements are being added, but I'm of course facing new problems, one of which is the performance.
Currently, I'm having 3 threads, two of which perform calculations, the other one updates the canvas. These three threads are synchronized with a CyclicBarrier, to have all calculations finished when beginning to draw the canvas.
I'm using several bitmaps in different sizes. In the drawing method, bitmaps are being rotated (by using drawBitmap-matrix combination with scaling/translating/rotating added into the matrix for "native" (i guess) management of it) and of course drawn. The problem I am facing is that whenever I have too many "moving and rotating" elements on the screen, it gets choppy.
Matrix matrix = new Matrix();
matrix.setTranslate(view.getX(), view.getY());
matrix.preScale((1.0f * view.getWidth() / view.getCurrentBitmap().getWidth()), (1.0f * view.getHeight() / view.getCurrentBitmap().getHeight()));
matrix.postRotate(view.getRotation(), view.getX() + view.getWidth()/2f, view.getY() + view.getHeight()/2f);
mCanvas.drawBitmap(view.getCurrentBitmap(), matrix, mBasicPaint);
For example, this is how the player-object is being drawn according to the rotation and position:
private void drawPlayer(final Canvas mCanvas) {
final Bitmap playerBitmap = mPlayer.getCurrentBitmap();
mPlayer.nextFrame();
if(playerBitmap != null) {
if(mPlayer.getRotation() != 0) {
Matrix matrix = new Matrix();
matrix.setTranslate(-mPlayer.getCurrentBitmap().getWidth()/2f, -mPlayer.getCurrentBitmap().getHeight()/2f);
matrix.postRotate(mPlayer.getRotation());
matrix.postTranslate(mPlayer.getX() + + mPlayer.getCurrentBitmap().getWidth()/2f, mPlayer.getY() + mPlayer.getCurrentBitmap().getHeight()/2f);
matrix.postScale((1.0f * mPlayer.getWidth() / mPlayer.getCurrentBitmap().getWidth()), (1.0f * mPlayer.getHeight() / mPlayer.getCurrentBitmap().getHeight()), mPlayer.getX(), mPlayer.getY());
mCanvas.drawBitmap(mPlayer.getCurrentBitmap(), matrix, mBasicPaint);
} else {
Matrix matrix = new Matrix();
matrix.setScale((1.0f * mPlayer.getWidth() / mPlayer.getCurrentBitmap().getWidth()), (1.0f * mPlayer.getHeight() / mPlayer.getCurrentBitmap().getHeight()));
matrix.postTranslate(mPlayer.getX(), mPlayer.getY());
mCanvas.drawBitmap(mPlayer.getCurrentBitmap(), matrix, mBasicPaint);
}
} else log("bitmap = null!");
}
(This is a kind of deprecated version, the .getCurrentBitmap() calls are reduced to one in the current version.)
How could I improve the performance? Should I create some sort of a "Loading..." screen, in which I pre-load EVERY bitmap (in its biggest size) and a pre-rotated version of each bitmap? (This would result in, if I go with 2-4 degree steps, 90-180 versions of each bitmap, which seems kind of.. a lot?) Or would this, with the rotated bitmaps stored as well, be too much on the memory? I don't know anything about OpenGL etc, this is why I'm using a SurfaceView and no other game engine, and I'm sure it has to work like this as well - somehow.
You are creating a Matrix (matrix) and Bitmap (playerBitmap) object each time you call drawPlayer method. As far as I understand, you will call this method each frame. So, each frame, you are creating 2 large objects that need to be Garbage collected when you exit the method and this will slow down your frame rate. You can create only 2 objects matrix and playerBitmap as class level variables and refresh them on each call of drawPlayer, reducing the number of GC calls.
So this is my second question today, I might be pushing my luck
In short making a 3D first Person, where you can move about and look around.
In My OnDrawFrame I am using
Matrix.setLookAtM(mViewMatrix, 0, eyeX , eyeY, eyeZ , lookX , lookY , lookZ , upX, upY, upZ);
To move back, forth, sidestep left etc I use something like this(forward code listed)
float v[] = {mRenderer.lookX - mRenderer.eyeX,mRenderer.lookY - mRenderer.eyeY, mRenderer.lookZ - mRenderer.eyeZ};
mRenderer.eyeX += v[0] * SPEED_MOVE;
mRenderer.eyeZ += v[2] * SPEED_MOVE;
mRenderer.lookX += v[0] * SPEED_MOVE;
mRenderer.lookZ += v[2] * SPEED_MOVE;
This works
Now I want to look around and I tried to port my iPhone openGL 1.0 code. This is left/right
float v[] = {mRenderer.lookX - mRenderer.eyeX,mRenderer.lookY - mRenderer.eyeY, mRenderer.lookZ - mRenderer.eyeZ};
if (x > mPreviousX )
{
mRenderer.lookX += ((Math.cos(SPEED_TURN / 2) * v[0]) - (Math.sin(SPEED_TURN / 2) * v[2]));
mRenderer.lookZ += ((Math.sin(SPEED_TURN / 2) * v[0]) + (Math.cos(SPEED_TURN / 2) * v[2]));
}
else
{
mRenderer.lookX -= (Math.cos(SPEED_TURN / 2) *v[0] - Math.sin(SPEED_TURN / 2) * v[2]);
mRenderer.lookZ -= (Math.sin(SPEED_TURN / 2) *v[0] + Math.cos(SPEED_TURN / 2) * v[2]);
}
This works for like 35 degrees and then goes mental?
Any ideas?
First of all I would suggest not to trace the look vector but rather forward vector, then in lookAt method use eye+forward to generate look vector. This way you can loose the update on the look completely when moving, and you don't need to compute the v vector (mRenderer.eyeX += forward.x * SPEED_MOVE;...)
To make things more simple I suggest that you normalize the vectors forward and up whenever you change them (and I will consider as you did in following methods).
Now as for rotation there are 2 ways. Either use right and up vectors to move the forward (and up) which is great for small turning (I'd say about up to 10 degrees and is capped at 90 degrees) or compute the current angle, add any angle you want and recreate the vectors.
The first mentioned method on rotating is quite simple:
vector forward = forward
vector up = up
vector right = cross(forward, up) //this one might be the other way around as up, forward :)
//going left or right:
forward = normalized(forward + right*rotationSpeedX)
//going up or down:
forward = normalized(forward + up*rotationSpeedY)
vector right = cross(forward, up) //this one might be the other way around
vector up = normalized(cross(forward, right)) //this one might be the other way around
//tilt left or right:
up = normalized(up + right*rotationZ)
The second method needs a bit trigonometry:
Normally to compute an angle you could just call atan(forward.z/forward.x) and add some if statements since the produced result is only in 180 degrees angle (I am sure you will be able to find some answers on the web to get rotation from vector though). The same goes with up vector for getting the vertical rotation. Then after you get the angles you can easily just add some degrees to the angles and recreate the vectors with sin and cos. There is a catch though, if you rotate the camera in such way, that forward faces straight up(0,1,0) you need to get the first rotation from up vector and the second from forward vector but you can avoid all that if you cap the maximum vertical angle to something like +- 85 degrees (and there are many games that actually do that). The second thing is if you use this approach your environment must support +-infinitive or this atan(forward.z/forward.x) will brake if forward.x == 0.
And some addition about the first approach. Since I see you are trying to move around the 2D space your forward vector to use with movement speed should be normalized(forward.x, 0, forward.z), it is important to normalize it or you will be moving slower if camera tilts up or down more.
Second thing is when you rotate left/right you might want to force up vector to (0,1,0) + normalize right vector and lastly recreate the up vector from forward and right. Again you then should cap the vertical rotation (up.z should be larger then some small value like .01)
It turned out my rotation code was wrong
if (x > mPreviousX )
{
mRenderer.lookX = (float) (mRenderer.eyeX + ((Math.cos(SPEED_TURN / 2) * v[0]) - (Math.sin(SPEED_TURN / 2) * v[2])));
mRenderer.lookZ = (float) (mRenderer.eyeZ + ((Math.sin(SPEED_TURN / 2) * v[0]) + (Math.cos(SPEED_TURN / 2) * v[2])));
}
else
{
mRenderer.lookX = (float) (mRenderer.eyeX + ((Math.cos(-SPEED_TURN / 2) * v[0]) - (Math.sin(-SPEED_TURN / 2) * v[2])));
mRenderer.lookZ = (float) (mRenderer.eyeZ + ((Math.sin(-SPEED_TURN / 2) * v[0]) + (Math.cos(-SPEED_TURN / 2) * v[2])));
}
I have a java code for SVG drawing. It processes transforms including rotate, and does this very well, as far as I can see in numerous test pictures compared against their rendering in Chrome. Next what I need is to get actual object location, which is in many images declared via transforms. So I decided just to read X and Y from Matrix used for drawing. Unfortunately I get incorrect values for rotate transform, that is they do not correspond to real object location in the image.
The stripped down code looks like this:
Matrix matrix = new Matrix();
float cx = 1000; // suppose this is an object X coordinate
float cy = 300; // this is its Y coordinate
float angle = -90; // rotate counterclockwise, got from "rotate(-90, 1000, 300)"
// shift to -X,-Y, so object is in the center
matrix.postTranslate(-cx, -cy);
// rotate actually
matrix.postRotate(angle);
// shift back
matrix.postTranslate(cx, cy);
// debug goes here
float[] values = new float[9];
matrix.getValues(values);
Log.v("HELLO", values[Matrix.MTRANS_X] + " " + values[Matrix.MTRANS_Y]);
The log outputs the values 700 and 1300 respectively. I'd expect 0 and 0, because I see the object rotated inplace in my image (that is there is no any movement), and postTranslate calls should compensate each other. Of course, I see how these values are formed from 1000 and 300, but don't understand why. Once again, I point out that the matrix with these strange values is used for actual object drawing, and it looks correct. Could someone explain what happens here? Am I missing something? So far I have only one solution of my problem: just do not try to obtain position from rotate, do it only for explicit matrix and translate transforms. But this approach lacks generality, and anyway I thought matrix should have reasonable values (including offsets) for any transformation type.
The answer is that the matrix is an operator for space transformation, and should not be used for direct extraction of object position. Instead, one should get initial object coordinates, as specified in x and y attributes of an SVG tag, and apply the matrix on them:
float[] src = new float[2];
src[0] = cx;
src[1] = cy;
matrix.mapPoints(src);
After this we get proper location values in x and y variables.