I'm using LWJGL to create sort of a Minecraft Clone.
The problem is that i only get about 20 FPS. (with about 32 blocks in each direction)
Also when i change the chunk the view freezes for some milliseconds.
I'm loading the textures with Slick. To render the blocks I use VBOs for textures and vertices (no VAOs or Shaders or normals). Each block consists of 6 quads which consist out of 2 triangles. Faces between blocks aren't rendered. Every frame the rendering thread checks for all 32 * 32 chunks if they are loaded into a buffer and if they should be loaded (depending on player position). If they are needed the vbos are created and the handles are saved to a list. If the chunks are too far away, the buffers get deleted. While rendering, all vbos currently in the graphics cards ram (handles saved in list) are drawn.
How can i speed rendering up? Should i have the buffers in "loaded" all the time and draw only the objects needed? Can normals or VAOs or shaders help me get a drastic improvement? Still if i get the frame rate higher there will be mor blocks than just a plate and i'm planning to import .obj.
Minecraft renders in i think DisplayLists. Would that be a solution? Create a display list for every chunk or block and recompile them if block or chunk changes?
Edit: I measured with VisualVM that the most time consuming method is glDrawArrays().
Some source code:
Rendering:
public static void render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
for(VBO vbo : vbos)
{
//disable texture smoothing
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, vbo.texture);
glBindBuffer(GL_ARRAY_BUFFER, vbo.vertex_handle);
glVertexPointer(vbo.vertex_size, GL_FLOAT, 0, 0l);
glBindBuffer(GL_ARRAY_BUFFER, vbo.texture_handle);
glTexCoordPointer(vbo.texture_size, GL_FLOAT, 0, 0l);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glDrawArrays(GL_TRIANGLES, 0, vbo.vertices);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
}
Chunk loading detection:
int chunkX = (int)(camera.getX() * (-1) / GameData.CHUNK_SIZE);
int chunkZ = (int)(camera.getZ() * (-1) / GameData.CHUNK_SIZE);
for(int cx = 0;cx < GameData.NUMBER_OF_CHUNKS;cx++)
{
for(int cz = 0;cz < GameData.NUMBER_OF_CHUNKS;cz++)
{
boolean shouldBeLoaded = (Math.abs(chunkX - cx) <= 2 && Math.abs(chunkZ - cz) <= 2);
if(GameData.chunks[cx][cz] != shouldBeLoaded)
{
if(shouldBeLoaded)
Util.loadChunk(cx, cz);//loads every block in the chunk
else
Util.unloadChunk(cx, cz);
}
}
}
Block loading:
public void load()
{
try{
if(GameData.map[x][y+1][z] == null || GameData.map[x][y+1][z].id == 2)
top = Util.createQuad(x, y+1, z, 1, id);
}catch(IndexOutOfBoundsException e){};
try{
if(GameData.map[x][y-1][z] == null || GameData.map[x][y-1][z].id == 2)
bot = Util.createQuad(x, y, z, 1, id);
}catch(IndexOutOfBoundsException e){};
try{
if(GameData.map[x-1][y][z] == null || GameData.map[x-1][y][z].id == 2)
left = Util.createQuad(x, y, z, 0, id);
}catch(IndexOutOfBoundsException e){};
try{
if(GameData.map[x+1][y][z] == null || GameData.map[x+1][y][z].id == 2)
right = Util.createQuad(x+1, y, z, 0, id);
}catch(IndexOutOfBoundsException e){};
try{
if(GameData.map[x][y][z-1] == null || GameData.map[x][y][z-1].id == 2)
front = Util.createQuad(x, y, z, 2, id);
}catch(IndexOutOfBoundsException e){};
try{
if(GameData.map[x][y][z+1] == null || GameData.map[x][y][z+1].id == 2)
back = Util.createQuad(x, y, z+1, 2, id);
}catch(IndexOutOfBoundsException e){};
}
Util.createQuad()
public static VBO createQuad(float x, float y, float z, int axis, int id)
{
boolean xA = axis == 0;
boolean yA = axis == 1;//senkrecht
boolean zA = axis == 2;
FloatBuffer vertex_data = BufferUtils.createFloatBuffer(6 * 3);
if(xA)
{
vertex_data.put(new float[] { x, y, z, });
vertex_data.put(new float[] { x, y, z+1, });
vertex_data.put(new float[] { x, y+1, z+1, });
vertex_data.put(new float[] { x, y, z, });
vertex_data.put(new float[] { x, y+1, z, });
vertex_data.put(new float[] { x, y+1, z+1, });
}
if(yA)
{
vertex_data.put(new float[] { x, y, z, });
vertex_data.put(new float[] { x, y, z+1, });
vertex_data.put(new float[] { x+1, y, z+1, });
vertex_data.put(new float[] { x, y, z, });
vertex_data.put(new float[] { x+1, y, z, });
vertex_data.put(new float[] { x+1, y, z+1, });
}
if(zA)
{
vertex_data.put(new float[] { x, y, z, });
vertex_data.put(new float[] { x+1, y, z, });
vertex_data.put(new float[] { x+1, y+1, z, });
vertex_data.put(new float[] { x, y, z, });
vertex_data.put(new float[] { x, y+1, z, });
vertex_data.put(new float[] { x+1, y+1, z, });
}
FloatBuffer texture_data = BufferUtils.createFloatBuffer(6 * 2);
texture_data.put(new float[] { 0f, 0f, });
texture_data.put(new float[] { 1f, 0f, });
texture_data.put(new float[] { 1f, 1f, });
texture_data.put(new float[] { 0f, 0f, });
texture_data.put(new float[] { 0f, 1f, });
texture_data.put(new float[] { 1f, 1f, });
vertex_data.flip();
texture_data.flip();
int vbo_vertex_handle = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo_vertex_handle);
glBufferData(GL_ARRAY_BUFFER, vertex_data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
int vbo_texture_handle = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo_texture_handle);
glBufferData(GL_ARRAY_BUFFER, texture_data, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
VBO vbo = new VBO(vbo_vertex_handle, vbo_texture_handle, renderEngine.textures.get(id), 6, 3, 2);
if(id == 2)//transparent
renderEngine.vbos.add(vbo);
else
renderEngine.vbos.add(0, vbo);;
return vbo;
}
I am currently using display lists because I'm still new to lwjgl and found VBO's pretty confusing, What I do is only check if the player moved 16 blocks or more, or removed/placed a block And that's when I update my lists, Otherwise I don't update the lists, You should also stop rendering any faces that you cannot see, So while checking for which faces to draw, You should also check if they are not in your view range, Here's an example of what I do; This example checks if I should render the top face of a certain block: if(PlayerY < BlockY) { then return false; }, This way you don't need to render something that you probably can't see.
Using display lists I get around 200 - 300 FPS for a view range of 48*48, At 128 you would get around 20 - 100 FPS.
EDIT:
You should also start using a texture atlas if you're not already, That reduces the bind() calls, Which makes it SO MUCH faster.
So good luck, And hope I helped :)
Related
I am implementing 3D modeling with LibGDX, and I want to manually rotate objects with the mouse, but I just I can't find any tutorials and examples that are right.
EDIT: Originally I only asked this question regarding rotating models, but I've discovered the same problem exists when rotating the camera.
Click here for the source code of a fully functional demo in github.com
Here's a snapshot of rotating the view and a model:
I want the object to rotate in the direction it is dragged by the mouse, no matter which direction it happens to be orientated at the time. As it is now, when I first drag the mouse to the right, the object rotates to the right about the screen Y axis as expected; but then when I drag the mouse upward I want the object to rotate upward about the screen X axis, but instead it spins to the left about the screen Z axis. Think of it like a floating ball in a bowl of water - whichever way you swipe at it, it rotates in that direction.
It seems to me that the mouse movement is transforming the objects directly in their local coordinate system; but instead I think it needs to transform the axis of rotation itself from the Screen Coordinate System into the Object Coordinate System before applying it to the object. I don't know, but it may be even more complicated than that.
I would really appreciate any insight or help to resolve this; I'm running out of hair to pull out... Thanks in advance.
LibGDX implements OpenGL. The terminology we use in OpenGL can help us to know how LibGDX works behind the scene. The other technology that implements OpenGL is WebGL, WebGL uses JavaScript. LibGDX uses Java. Once we know how OpenGL works, drawing objects and rotating objects should be fun. Of course depending on what we are drawing. OpenGL is well
documented. OpenGL itself always works the same. The first question, should be what are we drawing? And what are the objectives of the project. So, you want to draw the cube and rotate it. Cool. Once we can draw one cube and rotate it, we can add more objects in the scene. Cool. Strategically you can divide your project into parts.
Draw your object.
Rotate it.
Add more objects.
Rotate them.
We are done.
If you want to rotate the view as well, you can use the same process as above with some modifications. For example:
Draw the view.
Draw objects inside the view.
Rotate the objects.
Rotate the view.
On the other hand you can just use the camera and move it around the scene.
Done.
To make things worse LibGDX can extend many different classes, the programmer have to implement all abstract methods. Your code might look differently or some functions behave differently depending on which class you extend or implement in your project. The documentation about those classes is stochastic. Each and every abstract class comes with its abstract methods. The programmer should release any other resources allocated by LibGDX by using dispose() method. With just few changes your code should work as expected.
For Example:
//
package com.mygdx.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputProcessor;
//import etc...
public class Space extends ApplicationAdapter implements ApplicationListener, InputProcessor {
SpriteBatch batch;
BitmapFont font;
float backgroundColor[];
Cube cubes[];
ModelBatch modelBatch;
int selectedCube;
PerspectiveCamera camera;
Environment environment;
int xCubes = 27;
int touchedButton;
int lastX, lastY;
float Theta, Phi, dX, dY;
Vector3 screenAOR;
float screenAng;
float point[];
int side[];
int front[];
float w;
float h;
Model viewM;
ModelInstance viewMin;
Vector3 position;
ColorAttribute attrib;
Vector3 cubePositions[];
boolean drag;
#Override
public void create () {
drag = false;
Theta = 0;
Phi = 0;
batch = new SpriteBatch();
font = new BitmapFont();
font.setColor(Color.FOREST);
w = Gdx.graphics.getWidth();
h = Gdx.graphics.getHeight();
modelBatch = new ModelBatch();
screenAOR = new Vector3();
camera = new PerspectiveCamera(67f, 3f, 2f);
camera.position.set(10f, -10f, 70f);
camera.lookAt(Vector3.Zero);
camera.up.set(Vector3.Y);
camera.near = 1f;
camera.far = 500f;
camera.update();
backgroundColor = new float[]{.9f, .9f, .7f};
environment = new Environment();
environment.set(new ColorAttribute( ColorAttribute.AmbientLight, .6f, .6f, .6f, 1f));
environment.add(new DirectionalLight().set(.8f, .8f, .8f, 50f, 50f, 50f));
environment.add(new DirectionalLight().set(.5f, .5f, .5f, -50f, -50f, 50f));
spaceModel();
Gdx.input.setInputProcessor(this);
//Gdx.graphics.requestRendering();
}
#Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
//Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//Gdx.gl.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
Gdx.gl.glEnable(GL20.GL_CULL_FACE);
batch.begin();
modelBatch.begin(camera);
modelBatch.render(viewMin, environment);
for(int i = 0; i < cubes.length; i++){
modelBatch.render(cubes[i].modelInstance, environment);
}
font.draw(batch, "Space pro...", 10, 100);
batch.end();
modelBatch.end();
}
#Override
public void dispose () {
batch.dispose();
modelBatch.dispose();
font.dispose();
viewM.dispose();
}
/*///////////////////////////////////
//Implements all abstract methods.
//
*/////////////////////////////////
#Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
//lastX -= screenX;
//lastY -= screenY;
//float aspRatio = w/h;
//float angle = 40.0f;
moveModel(screenX, screenY);
// distance of mouse movement
//screenAng = (float) Math.sqrt( ((lastX * lastX) + (lastY * lastY)) );
//screenAng = (float) Math.tan((angle * 0.5)* (Math.PI/180));
// direction vector of the AOR
//screenAOR.set((lastY/screenAng), (lastX/screenAng), 0f );
//screenAOR.set(projection(angle,aspRatio,h,w));
//public Vector3 set(float x, float y, float z)
screenAOR.set(dX, dY, 0f);
if ( touchedButton == 0 ){
//public Matrix4 rotate(Vector3 axis, float degrees)
//cubes[ selectedCube ].modelInstance.transform.rotate( screenAOR, screenAng );
//public Matrix4 rotate(float axisX, float axisY, float axisZ, float degrees)
cubes[ selectedCube ].modelInstance.transform.rotate(dX, dY, 0f, Theta);
cubes[ selectedCube ].modelInstance.transform.rotate(dX, dY, 0f, Phi);
}
else{
//public void rotateAround(Vector3 point, Vector3 axis, float angle)
//camera.rotateAround( Vector3.Zero, screenAOR, (-screenAng/5.5f) );
//public void rotate(float angle, float axisX, float axisY, float axisZ)
//camera.rotate(Theta, dX, dY, 0f);
//camera.rotate(Phi, dX, dY, 0f);
//camera.rotateAround(position, screenAOR, Theta);
camera.rotateAround(Vector3.Zero, screenAOR, Theta);
camera.update();
//camera.rotateAround(position, screenAOR, Phi);
camera.rotateAround(Vector3.Zero, screenAOR, Phi);
camera.update();
viewMin.transform.rotate(dX, dY, 0f, Theta);
viewMin.transform.rotate(dX, dY, 0f, Phi);
}
//Gdx.graphics.requestRendering();
//Gdx.app.log("touchDragged:", screenAng+" : "+screenAOR+" : "+touchedButton);
return true;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
drag = true;
if(button == Buttons.LEFT){
touchedButton = 0;
}
else{
touchedButton = button;
}
Gdx.app.log("touchDown:", button+" : "+screenX+" : "+screenY+" : "+pointer);
return true;
}
#Override
public boolean keyDown(int i) {
float move = 1.0f;
float pX = w/10;
float pY = h/10;
if(i == Keys.LEFT){
pX -= move;
//public void rotate(float angle, float axisX, float axisY, float axisZ)
//camera.rotate(-45, pX, 0f, 0f);
camera.rotate(-45, 0f, pY, 0f);
//camera.update();
//public void translate(float x, float y, float z)
//camera.translate(move, 0f, 0f);
}
if(i == Keys.RIGHT){
pX += move;
//camera.rotate(45, pX, 0f, 0f);
camera.rotate(45, 0f, pY, 0f);
//camera.update();
//camera.translate(-move, 0f, 0f);
}
if(i == Keys.DOWN){
pY -= move;
//camera.rotate(-45, 0f, pY, 0f);
//camera.rotate(-45, pX, 0f, 0f);
camera.rotate(45, pX, 0f, 0f);
//camera.update();
//camera.translate(0f, 0f, move);
//camera.update();
//camera.translate(0f, move, 0f);
}
if(i == Keys.UP){
pY += move;
//camera.rotate(45, 0f, pY, 0f);
//camera.rotate(45, pX, 0f, 0f);
camera.rotate(-45, pX, 0f, 0f);
//camera.update();
//camera.translate(0f, 0f, -move);
//camera.update();
//camera.translate(0f, -move, 0f);
}
camera.update();
Gdx.app.log("KeyDown: ", pX+" : "+pY+" : "+i);
return true;
}
#Override
public boolean keyUp(int i) {
Gdx.app.log("KeyUp: ",i+" : ");
return false;
}
#Override
public boolean keyTyped(char c) {
//Gdx.app.log("KeyTyped: ",c+" : ");
return false;
}
#Override
public boolean touchUp(int i, int i1, int i2, int i3) {
drag = false;
Gdx.app.log("touchUp: ",i+" : "+i1+" : "+i2+" : "+i3);
return true;
}
#Override
public boolean mouseMoved(int i, int i1) {
if(!drag)
{
dX *= 0.96;
dY *= 0.96;
Theta += dX;
Phi += dY;
return false;
}
Gdx.app.log("mouseMoved: ", i+" : "+i1);
return false;
}
#Override
public boolean scrolled(int i) {
return false;
}
public void moveModel(int x2, int y2){
dX = (float) ((x2 - lastX)*2*(Math.PI/w));
dY = (float) ((y2 - lastY)*2*(Math.PI/h));
Theta += dX;
Phi += dY;
lastX = x2;
lastY = y2;
}
public void spaceModel(){
xCubes = 27;
selectedCube = 14;
ModelBuilder modelB = new ModelBuilder();
attrib = new ColorAttribute(1,Color.VIOLET);
Material m = new Material();
m.set(attrib);
//public Model createXYZCoordinates(float axisLength, Material material, long attributes)
viewM = modelB.createXYZCoordinates(w, m , 1);
cubePositions = new Vector3[xCubes];
for(int i = 0; i < xCubes; i++){
cubePositions[i] = new Vector3((i/9), ((i%9)/3), (i%3)).scl(20f).add(-20f, -20f, -20f);
}
cubes = new Cube[xCubes];
for(int i = 0; i < xCubes; i++){
cubes[i] = new Cube(cubePositions[i], (i == selectedCube));
}
viewMin = new ModelInstance(viewM);
position = cubePositions[0];
viewMin.transform.setTranslation(position);
Gdx.app.log("viewModel: ", w+" : "+h+" : "+w/h);
}
float[] projection(float angle, float a, float z1, float z2){
float ang = (float) Math.tan((angle * 0.5)* (Math.PI/180));
float[] proj = {
(float)0.5/ang, 0, 0, 0,
0, (float)0.5*a/ang, 0, 0,
0, 0, -(z2+z1)/(z2-z1), -1,
0, 0, (-2*z2*z1)/(z2-z1), 0
};
return proj;
}
}
/*////////////////////
//Draw cubes.
//
*/////////////////
class Cube{
Vector3 position;
Model model;
ModelInstance modelInstance;
Cube(Vector3 cubePosition, boolean selected) {
position = cubePosition;
compose(selected);
}
//etc...
}
//
When you are rotating the camera and rotating the objects, the direction can change or sometimes reversed. Depending at what angle is the object and the camera are at, at that point in time. It's like looking at the review mirror facing the opposite direction. So user's position and orientation in the scene is also important.
//
//
When you are looking at an object spinning “ < ---” around the circle, logically it is going to change direction “ ---> ”. When it reaches the far end “ <---> ”. i.e. From right to left and from left to right. Of course the user will still be using the same button. When you press the other buttons the same logic follows. Different sequences of rotations can result in different images as well. The other option which is time consuming is: translate(rotate(scale(geometry))). Eventually the entire image will be a single whole that is composed of its various parts. This process mighty help you to debug your code, and figuring out what caused the errors. With a little bit of maths, things can't get any worse. The science behind the object you are rotating is also important. When you are looking at the object rotating, the beauty is on the observers eye. e.g. Are you looking at the front side or the back side? Finally you have to use maths to get your model to behave the way you want it to behave.
Enjoy.
I asked the same question on one of the other sister forums, and got an answer that I was able to implement.
See the discussion here.
Here is the change to the code that made the whole thing work correctly:
Here is the change to the code that made the whole thing work correctly:
#Override public boolean touchDragged( int screenX, int screenY, int pointer )
{
lastX -= screenX;
lastY -= screenY;
// distance of mouse movement
screenAng = Vector3.len( lastX, lastY, 0f );
// direction vector of the AOR
screenAOR.set( lastY/screenAng, lastX/screenAng, 0f );
if ( touchedButton == 0 )
{ // rotate the part
// transform the screen AOR to a model rotation
Matrix4 camT, camR, camRi, modT, modR;
camT = new Matrix4();
camR = new Matrix4();
modT = new Matrix4();
modR = new Matrix4();
decompose( camera.view, camT, camR );
camRi = camR.cpy().inv();
decompose( cubes[ selectedCube ].modelInstance.transform, modT, modR );
tempMat.idt()
.mul( modT )
.mul( camRi )
.rotate( screenAOR, -screenAng )
.mul( camR )
.mul( modR );
cubes[ selectedCube ].modelInstance.transform.set( tempMat );
}
else if ( touchedButton == 1 )
{ // rotate the camera
// transform the AOR from screen CS to camera CS
// get the camera transformation matrix
tempMat.set( camera.view );
tempMat.translate( camera.position );
tempMat.inv();
// transform the screen AOR to a world AOR
worldAOR = transform( tempMat, screenAOR, worldAOR ).nor();
// apply the rotation of the angle about the world AOR to the camera
camera.rotateAround( Vector3.Zero, worldAOR, screenAng/5.5f );
camera.update();
}
lastX = screenX;
lastY = screenY;
Gdx.graphics.requestRendering();
return true;
}
Vector3 transform( Matrix4 mat, Vector3 from, Vector3 to )
{
// transform a vector according to a transformation matrix
to.x = from.dot( mat.val[ Matrix4.M00 ], mat.val[ Matrix4.M01 ],
mat.val[ Matrix4.M02 ] )+mat.val[ Matrix4.M03 ];
to.y = from.dot( mat.val[ Matrix4.M10 ], mat.val[ Matrix4.M11 ],
mat.val[ Matrix4.M12 ] )+mat.val[ Matrix4.M13 ];
to.z = from.dot( mat.val[ Matrix4.M20 ], mat.val[ Matrix4.M21 ],
mat.val[ Matrix4.M22 ] )+mat.val[ Matrix4.M23 ];
return to;
}
void decompose( Matrix4 m, Matrix4 t, Matrix4 r )
{
Matrix4 I4 = new Matrix4(); // Identity
for ( int i = 0; i < 4; i++ )
{
for ( int j = 0; j < 4; j++ )
{
if (i == 3 || j == 3)
{
r.val[ i*4+j ] = I4.val[ i*4+j ];
t.val[ i*4+j ] = m.val[ i*4+j ];
}
else
{
r.val[ i*4+j ] = m.val[ i*4+j ];
t.val[ i*4+j ] = I4.val[ i*4+j ];
}
}
}
}
Can someone help to make a static analogue clock in OpenGL 2.0 using Java? I used Bresenham Circle to draw a circle for the clock but is there any other way to draw a circle here? Then I drew an hour, minute, and second hand but I got an error in the render part while drawing the numbers. Can someone help me here with how to fix this? The code which I tried is following:
public class MyClock implements GLEventListener {
private int windowWidth = 1000;
private int windowHeight = 900;
public void display(GLAutoDrawable drawable) {
render(drawable);
}
//render
private void render(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
gl.glPointSize(5.0f);
//drawing circle
bresenhamCircle(new Point(100,100), 200, new Color(1, 0, 1), gl);
double x, y, d, delta_SE, delta_E;
x = 0;
double r = 200;
y = r;
d = 5-4*r;
setPixel(x ,y, gl);
setPixel(-x,y, gl);
setPixel(x,-y, gl);
setPixel(-x,-y, gl);
setPixel(y,x, gl);
setPixel(-y,x, gl);
setPixel(y,-x, gl);
setPixel(-y,-x, gl);
while (y>x) {
if (d >= 0) {
delta_SE = 4*(2*(x-y)+5);
d+=delta_SE; x++; y--;
}
else {
delta_E = 4*(2*x+3);
d+=delta_E; x++;
}
setPixel(x,y, gl);
setPixel(-x,y, gl);
setPixel(x,-y, gl);
setPixel(-x,-y, gl);
setPixel(y,x, gl);
setPixel(-y,x, gl);
setPixel(y,-x, gl);
setPixel(-y,-x, gl);
}
//hour hand
gl.glColor3d(0, 0, 1);
gl.glBegin(GL2.GL_LINES);
gl.glVertex2d(0, 00);
gl.glVertex2d(70, 70);
gl.glEnd();
//minute hand
gl.glColor3d(0, 0, 1);
gl.glBegin(GL2.GL_LINES);
gl.glVertex2d(0, 00);
gl.glVertex2d(150, 20);
gl.glEnd();
//seconds hand
gl.glColor3d(1, 0, 0);
gl.glBegin(GL2.GL_LINES);
gl.glVertex2d(0, 00);
gl.glVertex2d(120, -120);
gl.glEnd();
//drawing numbers
int AngleX, AngleY;
int radius;
double line;
for (int i=1;i<=12;i++) {
line = i/12*Math.PI*2;
radius=170;
AngleX=(int)((0)+(Math.sin(line)*radius));
AngleY=(int)((0)+(Math.cos(line)*radius));
gl.glColor3d(1, 1, 1);
String a= Integer.toString(i);
g.drawString(a,AngleX,AngleY);
}
}
//Bresenham Circle method
public void bresenhamCircle(Point center, double radius, Color color, GL2 gl) {
gl.glColor3d(0, 0, 1);
gl.glBegin(GL2.GL_POINTS);
gl.glVertex2d(00,200);
gl.glEnd();
}
private void setPixel(double x, double y,GL2 gl) {
gl.glColor3d(0.0, 1.0, 0.0);
gl.glBegin(GL2.GL_POINTS);
gl.glVertex2d(0,0);
gl.glVertex2d( x, y);
gl.glEnd();
}
public void dispose(GLAutoDrawable drawable) {
}
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glViewport(0, 0, windowWidth, windowHeight);
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(-windowWidth / 2, windowWidth / 2, -windowHeight / 2, windowHeight / 2, 0, 1);
gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
gl.glEnable(GL2.GL_LINE_SMOOTH);
}
public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
GL2 gl = drawable.getGL().getGL2();
windowWidth = w;
windowHeight = h;
gl.glViewport(0, 0, w, h);
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(-w / 2, w / 2, -h / 2, h / 2, 0, 1);
}
}
As far as I know, there isn't a simple method built into OpenGL ES 2.0 that draws text. That means you have to either find an open-source library that you can use, or you can create your own way of rendering text. This post outlines nicely the different ways that you can render text with OpenGL.
The best way I have found based on my research and which is also stated in the above link, is to render text through images. This tutorial shows how to create a basic text engine and is what I used to get an idea of how to render custom text. The idea is to take a texture atlas (a texture atlas is one image that contains all the letters, numbers, characters that you want to be able to render) and based on what string you want to draw, the engine will crop out the necessary letters, numbers, or characters from the atlas needed for your string and combine them into a square polygon that you can then render to the screen. The tutorial that I linked is a basic text engine, but once you understand how it works, you can then modify and improve it to your needs.
In a middle of developing my game I created a function to rotate a 2d triangle.
But the problem is that when I rotate an object, the rotation is very weird.
If the object's center is at the origin (the (0,0) point) of the axis then it rotates fine, but if I move the object then it rotates in a very weird way that it shouldn't be rotating in.
as you can see here is the code of the function.
public void rotate(float angle){
float x1 = x, y1 = y;
float x2 = x + width, y2 = y;
float x3 = x + width, y3 = y + height;
float x4 = x, y4 = y + height;
// Vertices in object space:
float[] vector1 = {x1, y1, 0f, 1f};
float[] vector2 = {x2, y2, 0f, 1f};
float[] vector3 = {x3, y3, 0f, 1f};
float[] vector4 = {x4, y4, 0f, 1f};
// Vertices in world space:
float[] vector1r = new float[4];
float[] vector2r = new float[4];
float[] vector3r = new float[4];
float[] vector4r = new float[4];
// Calculate the vertices in world space:
Matrix.multiplyMV(vector1r, 0, modelMatrix, 0, vector1, 0);
Matrix.multiplyMV(vector2r, 0, modelMatrix, 0, vector2, 0);
Matrix.multiplyMV(vector3r, 0, modelMatrix, 0, vector3, 0);
Matrix.multiplyMV(vector4r, 0, modelMatrix, 0, vector4, 0);
// Get the middle of the rectangle:
float[] vecMid = {(vector1r[0] + vector3r[0]) / 2,
(vector1r[1] + vector3r[1]) / 2, 0f, 1f};
move(vecMid[0], vecMid[1]); // Move to the origin
Matrix.rotateM(modelMatrix, 0, angle, 0, 0, 1f); // Rotate the object
move(-vecMid[0], -vecMid[1]); // Move back to place
if(LoggerConfig.ON)
Log.d("Middle Vertex", vecMid[0] + ", " + vecMid[1]);
}
The move method:
public void move(float x, float y){
Matrix.translateM(modelMatrix, 0, x, y, 0);
}
Can someone tell me what the problem is?
EDIT: I did some modifications to the code and added the move method.
The Original Problem:
I'm making a 2d game in OpenGL that uses tiles to draw a map. At the moment, each tile is drawn individually on a quad. The code for the tile rendering is:
#Override
public void render() {
if (parentTileSet.getTexture() == null)
return;
float alpha = parentLayer.getOpacity();
if (alpha < 0)
alpha = 0;
glColor4f(1.0f, 1.0f, 1.0f, alpha); //Set alpha to parent layer's alpha
parentTileSet.getTexture().bind(); //Bind texture
float bx = parentTileSet.getTileWidth() / 2; //Half the width
float by = parentTileSet.getTileHeight() / 2; //Half the height
float x = getX(), y = getY();
float z = 0f;
glPushMatrix(); //Save the current view matrix
glTranslatef(x, y, 0f); //Translate to the tile's position
glRotatef(rotate, 0f, 0f, 1f); //Rotate the tile if it has a rotation
if ((flipType & 1) > 0) //Are we flipping horizontally?
glScalef(-1f, 1f, 1f);
if ((flipType & 2) > 0) //Are we flipping vertically?
glScalef(1f, -1f, 1f);
//Draw the tile
glBegin(GL_QUADS);
glTexCoord2f(tex_cords.bottom_left.x, tex_cords.bottom_left.y); //bottom left
glVertex3f(-bx, -by, z);
glTexCoord2f(tex_cords.bottom_right.x, tex_cords.bottom_right.y); //bottom right
glVertex3f(bx, -by, z);
glTexCoord2f(tex_cords.top_right.x, tex_cords.top_right.y); //top right
glVertex3f(bx, by, z);
glTexCoord2f(tex_cords.top_left.x, tex_cords.top_left.y); //top left
glVertex3f(-bx, by, z);
glEnd();
glPopMatrix(); //Reload the view matrix to original state
parentTileSet.getTexture().unbind(); //Unbind the texture
}
And the result looks like the following:
http://i.stack.imgur.com/maIXk.jpg
Which is good. However, the FPS could be better, and this especially becomes an issue when the maps become more detailed. (The FPS drops to about 20).
My Ideal Solution:
Since the tiles will never change position or animate, if I render all the tiles to a texture once, and just draw that texture, then performance will increase!
So, I made a framebuffer using the following class:
public class Framebuffer implements Drawable {
private int fID, tID;
protected int width, height;
public Framebuffer(int width, int height) {
this.width = width;
this.height = height;
}
public void generate() {
fID = glGenFramebuffers();
tID = glGenTextures();
glBindFramebuffer(GL_FRAMEBUFFER, fID);
glBindTexture(GL_TEXTURE_2D, tID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_INT, (ByteBuffer) null);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tID, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
throw new RuntimeException("Framebuffer configuration error.");
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
public void begin() {
glPushMatrix();
glPushAttrib(GL_VIEWPORT_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, fID);
glViewport(0, 0, width, height);
}
public void end() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glPopMatrix();
glPopAttrib();
}
#Override
public void render() {
float x = 0f;
float y = 0f;
float z = 0f;
float bx = (this.width)/2f;
float by = (this.height)/2f;
glPushMatrix();
glBindTexture(GL_TEXTURE_2D, tID);
glBegin(GL_QUADS);
glTexCoord2f(0f, 0f); //bottom left
glVertex3f(x - bx, y - by, z);
glTexCoord2f(1f, 0f); //bottom right
glVertex3f(x + bx, y - by, z);
glTexCoord2f(1f, 1f); //top right
glVertex3f(x + bx, y + by, z);
glTexCoord2f(0f, 1f); //top left
glVertex3f(x - bx, y + by, z);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glPopMatrix();
}
And then use the following code to generate the framebuffer, and render the tiles to the texture:
System.out.println("Attempting to generate frame buffer..");
try {
Framebuffer frame = new Framebuffer(tiledData.getPixelWidth(), tiledData.getPixelHeight());
frame.generate();
frame.begin();
glScalef(0.5f, 0.5f, 1f); //The game is scaled up by 2, so I'm just unscalling here.
Iterator<Drawable> drawableIterator = getSortedDrawables();
while (drawableIterator.hasNext()) {
Drawable d = drawableIterator.next();
if (d instanceof TileObject) {
TileObject t = (TileObject)d;
if (t.isGroundLayer() && !t.isParallaxLayer() && !t.isAnimated()) {
t.render(); //This render method is the one from above
drawableIterator.remove();
}
}
}
frame.end();
System.out.println("Success!");
addDrawable(frame);
} catch (RuntimeException e) {
System.out.println("Framebuffers are not supported!");
}
However, the output is this:
http://puu.sh/8Eemy.jpg
Which is not what I expected to happen. I tried scaling it, moving it, and all sorts of things but I can never get it exactly right.
My Question
What am I doing wrong? Is there another way to go about this? Am I missing something?
UPDATE
Thanks to the comments, I've got it to render correctly!
I changed the framebuffer class to be the following:
private int fID, tID;
protected int width, height;
public Framebuffer(int width, int height) {
this.width = width;
this.height = height;
}
public void generate() {
fID = glGenFramebuffers();
tID = glGenTextures();
glBindFramebuffer(GL_FRAMEBUFFER, fID);
glBindTexture(GL_TEXTURE_2D, tID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_INT, (ByteBuffer) null);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tID, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
throw new RuntimeException("Framebuffer configuration error.");
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
public void begin() {
glBindFramebuffer(GL_FRAMEBUFFER, fID);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, width, 0, height, -1, 1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
}
public void end() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glPopAttrib();
}
#Override
public void render() {
float x = this.width / 2f;
float y = this.height / 2f;
float z = 0f;
float bx = (this.width)/2f;
float by = (this.height)/2f;
glPushMatrix();
glBindTexture(GL_TEXTURE_2D, tID);
glBegin(GL_QUADS);
glTexCoord2f(0f, 0f); //bottom left
glVertex3f(x - bx, y - by, z);
glTexCoord2f(1f, 0f); //bottom right
glVertex3f(x + bx, y - by, z);
glTexCoord2f(1f, 1f); //top right
glVertex3f(x + bx, y + by, z);
glTexCoord2f(0f, 1f); //top left
glVertex3f(x - bx, y + by, z);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glPopMatrix();
}
The points of interest here is the begin() and end() method, where I setup a new projection matrix and save the old one, then restore it in the end() method.
How should the x, y, z, sizeX, sizeY, sizeZ values be put to the vertices to make a cube?
public static void cube(float x, float y, float z, float sx, float sy, float sz){
glPushMatrix();
{
glTranslatef(x, y, z);
//Just one side of the cube is given due to too much unnecessary code.
glBegin(GL_QUADS);
glVertex3f(-1, -1, 1);
glVertex3f(1, -1, 1);
glVertex3f(1, 1, 1);
glVertex3f(-1, 1, 1);
glEnd();
}
glPopMatrix();
}
Thanks.
Wherever in your code you have e.g. glVertex3f(-1, -1, 1); mulitply them with the corresponding value of sx, sy, sz divided by 2 e.g. glVertex3f(-sx/2, -sy/2, sz/2);
For the position you can issue a glTranslatef(x, y, z) before drawing the cube. If you insist on hardcoding this into the vertices then you should write the above statement as glVertex3f(x - sx/2, y - sy/2, z + sz/2);