Related
I want to rotate an element around a specific point defined by me and dynamically changed.
I am orientating myself at the guidelines from the google developers site.
My first approach is this:
scratch = new float[16];
Matrix.setIdentityM(mRotationMatrix, 0);
Matrix.setRotateM(mRotationMatrix, 0, angle, 0, 0, 1f);
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
element.draw(scratch);
This rotates the object around the center of the screen.
What do I have to add/change to make the object rotate around some other point?
Add a translation operation.
Grafika's Sprite2d class provides an example:
/**
* Re-computes mModelViewMatrix, based on the current values for rotation, scale, and
* translation.
*/
private void recomputeMatrix() {
float[] modelView = mModelViewMatrix;
Matrix.setIdentityM(modelView, 0);
Matrix.translateM(modelView, 0, mPosX, mPosY, 0.0f);
if (mAngle != 0.0f) {
Matrix.rotateM(modelView, 0, mAngle, 0.0f, 0.0f, 1.0f);
}
Matrix.scaleM(modelView, 0, mScaleX, mScaleY, 1.0f);
mMatrixReady = true;
}
This positions the object, then rotates it around the center of the object.
You need to translate the matrix in the reverse direction of the point first, then rotate and then translate it back. Look at it as if the rotation is always rotating around the center of the world, and the translation moves the center of the world.
Something like this (untested):
scratch = new float[16];
Matrix.setIdentityM(mRotationMatrix, 0);
Matrix.translateM(mRotationMatrix, 0, -x, -y, -z);
Matrix.rotateM(mRotationMatrix, 0, angle, 0, 0, 1f);
Matrix.translateM(mRotationMatrix, 0, x, y, z);
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
element.draw(scratch);
The x, y and z values need to be calculated as the delta between the current position of the object in the world and the position of the point you want to rotate around. You need to do that calculation yourself, but that's pretty trivial.
To add some lightning to my OpenGL ES20 cube, I need to calculate the normals for each plane. I've found a "tutorial" on lightning, but they simply hard-coded the normals into the cube, which appears to me as not the best option, since it seems limited?
So my approach to the cube is as follows:
private float[] mVertices = {
-1, -1, -1, // bottom left
1, -1, -1, // bottom right back
1, 1, -1, // top right
-1, 1, -1, // top left
-1, -1, 1, // bottom left
1, -1, 1, // bottom right front
1, 1, 1, // top right
-1, 1, 1 // top left
};
private float[] mColors = {
0.6f, 0f, 0.6f,
0.6f, 0f, 0.6f,
0.6f, 0f, 0.6f,
0.6f, 0f, 0.6f,
0.8f, 0f, 0.6f,
0.8f, 0f, 0.6f,
0.8f, 0f, 0.6f,
0.8f, 0f, 0.6f
};
private float[] mNormal = new float[?]; ?
private short[] mIndices = {
0, 4, 5,
0, 5, 1,
1, 5, 6,
1, 6, 2,
2, 6, 7,
2, 7, 3,
3, 7, 4,
3, 4, 0,
4, 7, 6,
4, 6, 5,
3, 0, 1,
3, 1, 2
};
i.e. i have all 8 vertices defined as well as indices, on how to combine them to triangles.
To get the normal matrix, I've read that I am supposed to invert the matrix, then transpose it. So I've tried this:
float[] mTempMatrix = new float[mVertices.length];
Matrix.invertM(mTempMatrix, 0, mVertices, 0);
Matrix.transposeM(mNormal, 0, mTempMatrix, 0);
But this is always filled with zeros, and my cube stays black. How example am I supposed to calculate the normal matrix? Should it be something with the model matrix? If yes, where am I supposed to do this, since the only place they are really combined is in the shader? Is there another way to do this which would be more appropriate?
The issue you are facing is that you share vertices between cube faces. The normal is a vector that points othogonal to the plane of the surface.
Consider the top/right/front vertex as an example (that you share with the front, right and top faces).
When used on the front face, the normal needs to point towards you as 0, 0, 1
When used on the right face, the normal needs to point to the right as 1, 0, 0
When used on the top face, the normal needs to point up as 0, 1, 0
So how to reconcile that?
You could set the normal for the vertex to point out from the corner, e.g. as 0.707, 0.707, 0.707. That'll most likely give you an interesting lighting effect on the corner but is probably not what you're after.
The other solution is not to re-use the vertices between faces. So you'll need 24 (4 per side) instead of 8. You'll then have 3 versions of each vertex but each one of those now belongs to just one face, hence you can set the normal vector as perpendicular to that face. You'll also be able to set the color for just that face as well since you'll no longer be sharing the vertex with other faces.
I'm trying to work on some OpenGL stuff. What I've got up to now is a viewport, in which I'm drawing some imaginary "borders" by using GL_LINES. It looks like this, with setLookAt set as follows:
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 5, 0, 0, 0, 0, 1, 0);
My frustum is set: Matrix.frustumM(mProjectionMatrix, 0, -2, 2, -2, 2, 1, 11); so I'm positioned somewhere inside the "cube".
Now what I'm trying to achieve is let the user look around. I'm capturing onTouchEvents, passing any movement in x/y direction to the renderer. What I'm doing next is rotating all lines drawn by the specific angle I received from the touch listener.
It then looks like this:
So the cube is not rotated around the viewer or the eye-center, but instead around some point that I don't know where it's coming from.
My problem is: how can I rotate the object around the viewer's center/position? Do I have to rotate the mViewMatrix which comes from setLookAtM? If yes, simply by using Matrix.setRotateM(mViewMatrix, ...)?
The Line's drawing method looks like this:
public void draw(float[] mViewMatrix, float[] mProjectionMatrix) {
Matrix.multiplyMM(mViewProjectionMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
GLES20.glUseProgram(iProgId);
lineBuffer.position(0);
GLES20.glVertexAttribPointer(iPosition, 3, GLES20.GL_FLOAT, false, 0, lineBuffer);
GLES20.glEnableVertexAttribArray(iPosition);
colorBuffer.position(0);
GLES20.glVertexAttribPointer(iColor, 3, GLES20.GL_FLOAT, false, 0, colorBuffer);
GLES20.glEnableVertexAttribArray(iColor);
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.rotateM(mModelMatrix, 0, rotX, 1, 0, 0);
Matrix.rotateM(mModelMatrix, 0, -rotY, 0, 1, 0);
Matrix.setIdentityM(mViewProjectionMatrix, 0);
Matrix.multiplyMM(mViewProjectionMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
//GLES20.glUniformMatrix4fv(iVPMatrix, 1, false, mMVPMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewProjectionMatrix, 0);
GLES20.glUniformMatrix4fv(iVPMatrix, 1, false, mMVPMatrix, 0);
//GLES20.glDrawElements(GLES20.GL_LINES, mVertices.length/2, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
GLES20.glDrawArrays(GLES20.GL_LINES, 0, mVertices.length / 2);
}
look at function takes 3 vertices, eye position, target position and up vector. Basically it generates a matrix that moves scene around to render scene like you are looking from eye position to target position. In your example your is at 0,0,0 and looks at 5, 0, 0 (so you are looking at +x direction and up is defined as 0, 1, 0 (higher y value means object will be at top of window.)
Instead of using those constants, first define a camera position.
float cameraX, cameraY, cameraY;
it is harder to work with target vectors, so instead use an angle that defines which direction you are looking at
float angle;
and to calculate targetVector use this angle.
float targetX = cameraX + cos(angle);
float targetY = cameraY;
float targetZ = cameraZ + sin(angle);
Now to move camera around, you have to modify cameraX-Y-Z. If you want to move forward you should move your camera towards to target vector. For example to move 10 unit forward.
targetX += cos(angle)*10;
targetY += sin(angle)*10;
You also need to recalculate target vector since target position also should move.
If you want to move backwards, use -= operator instead. If you want to move sides then you have to add or remove angle PI/2 in those calculations.
To rotate camera around just increase/decrease angle and recalculate target vector.
This is a very basic camera and you won't be able to look up or down. You have to use pitch/yaw camera to be able to look up and down.
i'am new in OpenGL ES. Can you helps me to calculate world coordinates of cube after rotate and translate. For example:
first i rotate cube:
gl.glRotatef(90, 1, 0, 0);
than change his position
gl.glTranslatef(10, 0, 0);
How can i calculate his "new" world coordinates? I read about glGetFloatv(GL_MODELVIEW_MATRIX , matrix) but not understand it. Maybe someone can provide sample code.
EDIT:
I found solution. Android code
float[] matrix = new float[] {
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1,
};
Matrix.rotateM(matrix, 0, rx, 1, 0, 0);
Matrix.rotateM(matrix, 0, ry, 0, 1, 0);
Matrix.rotateM(matrix, 0, rz, 0, 0, 1);
Matrix.translateM(matrix, 0, x, y, z);
x = matrix[12];
y = matrix[13];
z = matrix[14];
Thanks for answers.
Although you have an answer for the part you want, in terms of the rest of your question, you'd do something like (please forgive me if I make any Java errors, I'm not really an Android person):
float[] matrix = new float[16];
gl.glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
// check out matrix[12], matrix[13] and matrix[14] now for the world location
// that (0, 0, 0) [, 1)] would be mapped to
That getFloatv just reads back the current value of the modelview matrix into the float buffer specified. In OpenGL 4x4 matrices are specified so that index 0 is the top left, index 3 is the lowest number in the first column and 12 is the number furthest to the right in the first row. That's usually referred to as column-major layout, though the OpenGL FAQ isn't entirely happy with the term.
I'll start by saying that i'm REALLY new to OpenGL ES (I started yesterday =), but I do have some Java and other languages experience.
I've looked a lot of tutorials, of course Nehe's ones and my work is mainly based on that.
As a test, I started creating a "tile generator" in order to create a small Zelda-like game (just moving a dude in a textured square would be awsome :p).
So far, I have achieved a working tile generator, I define a char map[][] array to store wich tile is on :
private char[][] map = {
{0, 0, 20, 11, 11, 11, 11, 4, 0, 0},
{0, 20, 16, 12, 12, 12, 12, 7, 4, 0},
{20, 16, 17, 13, 13, 13, 13, 9, 7, 4},
{21, 24, 18, 14, 14, 14, 14, 8, 5, 1},
{21, 22, 25, 15, 15, 15, 15, 6, 2, 1},
{21, 22, 23, 0, 0, 0, 0, 3, 2, 1},
{21, 22, 23, 0, 0, 0, 0, 3, 2, 1},
{26, 0, 0, 0, 0, 0, 0, 3, 2, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
};
It's working but I'm no happy with it, I'm sure there is a beter way to do those things :
1) Loading Textures :
I create an ugly looking array containing the tiles I want to use on that map :
private int[] textures = {
R.drawable.herbe, //0
R.drawable.murdroite_haut, //1
R.drawable.murdroite_milieu, //2
R.drawable.murdroite_bas, //3
R.drawable.angledroitehaut_haut, //4
R.drawable.angledroitehaut_milieu, //5
};
(I cutted this on purpose, I currently load 27 tiles)
All of theses are stored in the drawable folder, each one is a 16*16 tile.
I then use this array to generate the textures and store them in a HashMap for a later use :
int[] tmp_tex = new int[textures.length];
gl.glGenTextures(textures.length, tmp_tex, 0);
texturesgen = tmp_tex; //Store the generated names in texturesgen
for(int i=0; i < textures.length; i++)
{
//Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), textures[i]);
InputStream is = context.getResources().openRawResource(textures[i]);
Bitmap bitmap = null;
try {
//BitmapFactory is an Android graphics utility for images
bitmap = BitmapFactory.decodeStream(is);
} finally {
//Always clear and close
try {
is.close();
is = null;
} catch (IOException e) {
}
}
// Get a new texture name
// Load it up
this.textureMap.put(new Integer(textures[i]),new Integer(i));
int tex = tmp_tex[i];
gl.glBindTexture(GL10.GL_TEXTURE_2D, tex);
//Create Nearest Filtered Texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
//Use the Android GLUtils to specify a two-dimensional texture image from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
I'm quite sure there is a better way to handle that... I just was unable to figure it. If someone has an idea, i'm all ears.
2) Drawing the tiles
What I did was create a single square and a single texture map :
/** The initial vertex definition */
private float vertices[] = {
-1.0f, -1.0f, 0.0f, //Bottom Left
1.0f, -1.0f, 0.0f, //Bottom Right
-1.0f, 1.0f, 0.0f, //Top Left
1.0f, 1.0f, 0.0f //Top Right
};
private float texture[] = {
//Mapping coordinates for the vertices
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
Then, in my draw function, I loop through the map to define the texture to use (after pointing to and enabling the buffers) :
for(int y = 0; y < Y; y++){
for(int x = 0; x < X; x++){
tile = map[y][x];
try
{
//Get the texture from the HashMap
int textureid = ((Integer) this.textureMap.get(new Integer(textures[tile]))).intValue();
gl.glBindTexture(GL10.GL_TEXTURE_2D, this.texturesgen[textureid]);
}
catch(Exception e)
{
return;
}
//Draw the vertices as triangle strip
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
gl.glTranslatef(2.0f, 0.0f, 0.0f); //A square takes 2x so I move +2x before drawing the next tile
}
gl.glTranslatef(-(float)(2*X), -2.0f, 0.0f); //Go back to the begining of the map X-wise and move 2y down before drawing the next line
}
This works great by I really think that on a 1000*1000 or more map, it will be lagging as hell (as a reminder, this is a typical Zelda world map : http://vgmaps.com/Atlas/SuperNES/LegendOfZelda-ALinkToThePast-LightWorld.png ).
I've read things about Vertex Buffer Object and DisplayList but I couldn't find a good tutorial and nodoby seems to be OK on wich one is the best / has the better support (T1 and Nexus One are ages away).
I think that's it, I've putted a lot of code but I think it helps.
Thanks in advance !
A couple of things:
There's no need to use a hashmap, just use a vector/list.
It may be faster/easier to have one large texture that contains all your tiles. Use appropriate texture coordinates to select the appropriate tile. You might have to be a little bit careful about texture filtering here. It sounds like you are doing a 2D game in which case you probably want to use nearest-neighbour filtering for the tiles and clamp the camera to integer pixel locations.
Wouldn't it be easier to use GL_QUADS rather than GL_TRIANGLE_STRIP. Not sure about your code there - you don't seem to use the 'texture' array.
The map size shouldn't make any difference, as long as you don't draw tiles that aren't on the screen. Your code should be something like:
.
int minX = screenLeft / tileSize;
int minY = screenBottom / tileSize;
int maxX = screenRight / tileSize;
int maxY = screenTop / tilesSize;
for (int x = minX; x <= maxX; ++x)
{
for (int y = minY; y < maxY; ++y)
{
...