I have recently created 2D height map grid which generates 3D terrain mesh for my world With the ability to add hills/bumps with mouse click events during runtime. My Problem is that every time i add to the height of the vertices i update the whole terrain's normal and position vbos(very not efficient). what is the way to
change specific part of vbo?
I have heared that glBufferSubData is the way, but How can i change only the Y value? (the vbo is x,y,z,x,y,z...)
and get the changed verticies in order for glBufferSubData?
Terrain class:
public class Terrain {
public static final int SIZE = 500;
//VAO, vertexCount, VBOS
private RawModel model;
//textures for the terrain
private terrainTexturePack texturePack;
Loader loader;
private static int VERTEX_COUNT =128;
float[] Vertices;
float[] Normals;
float[] TextureCoords;
int[] Indices;
private float[][] heights;
public Terrain(Loader loader, terrainTexturePack texturePack) {
this.texturePack = texturePack;
this.loader = loader;
this.model = generateTerrain(loader);
}
public RawModel getModel() {
return model;
}
public terrainTexturePack getTexturePack() {
return texturePack;
}
//player collision detection witn the terrain
public Vector3f getXYZOfTerrain(float worldX, float worldZ) {
float gridSquareSize = SIZE / ((float) heights.length - 1);
int gridX = (int) Math.floor(worldX / gridSquareSize);
int gridZ = (int) Math.floor(worldZ / gridSquareSize);
if(gridX >= heights.length - 1 || gridZ >= heights.length - 1 || gridX < 0 || gridZ < 0) {
return null;
}
float xCoord = (worldX % gridSquareSize)/gridSquareSize;
float zCoord = (worldZ % gridSquareSize)/gridSquareSize;
float yCoord;
if (xCoord <= (1-zCoord)) {
yCoord = Maths.barryCentric(new Vector3f(0, heights[gridX][gridZ], 0), new Vector3f(1,
heights[gridX + 1][gridZ], 0), new Vector3f(0,
heights[gridX][gridZ + 1], 1), new Vector2f(xCoord, zCoord));
} else {
yCoord = Maths.barryCentric(new Vector3f(1, heights[gridX + 1][gridZ], 0), new Vector3f(1,
heights[gridX + 1][gridZ + 1], 1), new Vector3f(0,
heights[gridX][gridZ + 1], 1), new Vector2f(xCoord, zCoord));
}
return new Vector3f(gridX, yCoord, gridZ);
}
//GENERATE THE TERRAIN
private RawModel generateTerrain(Loader loader) {
int pointer = 0;
int count = VERTEX_COUNT * VERTEX_COUNT;
heights = new float[VERTEX_COUNT][VERTEX_COUNT];
float[] vertices = new float[count * 3];
float[] normals = new float[count * 3];
float[] textureCoords = new float[count * 2];
int[] indices = new int[6 * (VERTEX_COUNT - 1) * (VERTEX_COUNT * 1)];
int vertexPointer = 0;
for (int i = 0; i < VERTEX_COUNT; i++) {
for (int j = 0; j < VERTEX_COUNT; j++) {
vertices[vertexPointer * 3] = (float) j / ((float) VERTEX_COUNT - 1) * SIZE;
float height = 0f;
vertices[vertexPointer * 3 + 1] = height;
heights[j][i] = height;
vertices[vertexPointer * 3 + 2] = (float) i / ((float) VERTEX_COUNT - 1) * SIZE;
Vector3f normal =new Vector3f(0, 1, 0);// calculateNormal(j, i, noise);
normals[vertexPointer * 3] = normal.x;
normals[vertexPointer * 3 + 1] = normal.y;
normals[vertexPointer * 3 + 2] = normal.z;
textureCoords[vertexPointer * 2] = (float) j / ((float) VERTEX_COUNT - 1);
textureCoords[vertexPointer * 2 + 1] = (float) i / ((float) VERTEX_COUNT - 1);
vertexPointer++;
if(i < VERTEX_COUNT - 1 && j < VERTEX_COUNT - 1){
int topLeft = (i * VERTEX_COUNT) + j;
int topRight = topLeft + 1;
int bottomLeft = ((i + 1) * VERTEX_COUNT) + j;
int bottomRight = bottomLeft + 1;
indices[pointer++] = topLeft;
indices[pointer++] = bottomLeft;
indices[pointer++] = topRight;
indices[pointer++] = topRight;
indices[pointer++] = bottomLeft;
indices[pointer++] = bottomRight;
}
}
}
Vertices = vertices;
TextureCoords = textureCoords;
Normals = normals;
Indices = indices;
return loader.loadToVAO(vertices, textureCoords, normals, indices);
}
//Calculate normal
private Vector3f calculateNormal(int x, int z) {
float heightL = Vertices[((( (z) *VERTEX_COUNT)+ (x-1) )*3)+1];
float heightR = Vertices[((( (z) *VERTEX_COUNT)+ (x+1) )*3)+1];
float heightD = Vertices[((( (z-1) *VERTEX_COUNT)+ (x) )*3)+1];
float heightU = Vertices[((( (z+1) *VERTEX_COUNT)+ (x) )*3)+1];
Vector3f normal = new Vector3f(heightL - heightR, 2f, heightD - heightU);
normal.normalise();
return normal;
}
//create mountain where the mouse clicked
//Vertices[(((y*VERTEX_COUNT)+x)*3)+1] = one Vertex in 2d grid
public void createHill(int x0, int y0){
float h = 0.06f;
int xs=VERTEX_COUNT;
int ys=VERTEX_COUNT;
float maxHeight =Vertices[(((y0*xs)+x0)*3)+1]+h;
float r = (9*maxHeight)/30;
//Loop the vertices
for(int y=(int) (y0-r);y<=y0+r;y++)
for(int x=(int) (x0-r);x<=x0+r;x++){
double circule = Math.sqrt((x-x0)*(x-x0)+(y0-y)*(y0-y));
if (circule <= r)
if ((x>=1)&&(x<xs-1))
if ((y>=1)&&(y<ys-1)){
Vertices[(((y*xs)+x)*3)+1] = Maths.hillsHeight(x0, x, y0, y,(maxHeight), r);
Vector3f normal = calculateNormal(x,y);
Normals[((((y*xs)+x))) * 3] = normal.x;
Normals[((((y*xs)+x))) * 3 + 1] = normal.y;
Normals[((((y*xs)+x))) * 3 + 2] = normal.z;
}
}
//change the whole VBO's not effective
//Note: i know that i dont need to update textures and indices
this.model=loader.loadToVAO(Vertices, TextureCoords, Normals, Indices);
}
}
Raw model class(vbo and vao holder):
//Store the VAOS and VBOS
public class RawModel {
private int vaoID;
private int vertexCount;
private int positionVbo;
private int normalVbo;
private int textureVbo;
public RawModel(int vaoID, int vertexCount, int positionVbo, int normalVbo, int textureVbo) {
this.vaoID = vaoID;
this.vertexCount = vertexCount;
this.positionVbo = positionVbo;
this.normalVbo = normalVbo;
this.textureVbo = textureVbo;
}
public RawModel(int vaoID, int vertexCount) {
this.vaoID = vaoID;
this.vertexCount = vertexCount;
}
public int getVaoID() {
return vaoID;
}
public int getVertexCount() {
return vertexCount;
}
public int getPositionVbo() {
return positionVbo;
}
public int getTextureVbo() {
return textureVbo;
}
public int getNormalVbo() {
return normalVbo;
}
}
loader class:
public class Loader {
//For clean up
private List<Integer> vaos = new ArrayList<Integer>();
private List<Integer> vbos = new ArrayList<Integer>();
private List<Integer> textures = new ArrayList<Integer>();
//Load mesh into VAO
public RawModel loadToVAO(float[] positions,float[] textureCoords,float[] normals,int[] indices){
int vaoID = createVAO();
bindIndicesBuffer(indices);
int positionvbo = storeDataInAttributeList(0,3,positions);
int textureVbo = storeDataInAttributeList(1,2,textureCoords);
int normalsnvbo = storeDataInAttributeList(2,3,normals);
unbindVAO();
return new RawModel(vaoID,indices.length, positionvbo, textureVbo, normalsnvbo);
}
//Load texture
public int loadTexture(String fileName) {
Texture texture = null;
try {
texture = TextureLoader.getTexture("PNG",
new FileInputStream("res/textures/" + fileName + ".png"));
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR_MIPMAP_LINEAR);
GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, -2);
if(GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){
float amount = Math.min(4f,
GL11.glGetFloat(EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT));
GL11.glTexParameterf(GL11.GL_TEXTURE_2D,
EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, amount);
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("Tried to load texture " + fileName + ".png , didn't work");
System.exit(-1);
}
textures.add(texture.getTextureID());
return texture.getTextureID();
}
//Clean up
public void cleanUp(){
for(int vao:vaos){
GL30.glDeleteVertexArrays(vao);
}
for(int vbo:vbos){
GL15.glDeleteBuffers(vbo);
}
for(int texture:textures){
GL11.glDeleteTextures(texture);
}
}
//Creates vao
private int createVAO(){
int vaoID = GL30.glGenVertexArrays();
vaos.add(vaoID);
GL30.glBindVertexArray(vaoID);
return vaoID;
}
//Store data in vbo
private int storeDataInAttributeList(int attributeNumber, int coordinateSize,float[] data){
int vboID = GL15.glGenBuffers();
vbos.add(vboID);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
FloatBuffer buffer = storeDataInFloatBuffer(data);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(attributeNumber,coordinateSize,GL11.GL_FLOAT,false,0,0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vboID;
}
private void unbindVAO(){
GL30.glBindVertexArray(0);
}
//Bind indices buffer
private void bindIndicesBuffer(int[] indices){
int vboID = GL15.glGenBuffers();
vbos.add(vboID);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboID);
IntBuffer buffer = storeDataInIntBuffer(indices);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
}
//Store in int buffer
private IntBuffer storeDataInIntBuffer(int[] data){
IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
buffer.put(data);
buffer.flip();
return buffer;
}
//Store in float buffer
private FloatBuffer storeDataInFloatBuffer(float[] data){
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
buffer.put(data);
buffer.flip();
return buffer;
}
//Load skyBox textures
public int loadCubeMap(String[] textureFiles){
int texID = GL11.glGenTextures();
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL13.GL_TEXTURE_CUBE_MAP, texID);
for(int i = 0; i < textureFiles.length; i++){
TextureData data = decodeTextureFile("res/textures/"+ textureFiles[i] + ".png");
GL11.glTexImage2D(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL11.GL_RGBA, data.getWidth(), data.getHeight(), 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data.getBuffer());
}
GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
textures.add(texID);
GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
return texID;
}
private TextureData decodeTextureFile(String fileName) {
int width = 0;
int height = 0;
ByteBuffer buffer = null;
try {
FileInputStream in = new FileInputStream(fileName);
PNGDecoder decoder = new PNGDecoder(in);
width = decoder.getWidth();
height = decoder.getHeight();
buffer = ByteBuffer.allocateDirect(4 * width * height);
decoder.decode(buffer, width * 4, Format.RGBA);
buffer.flip();
in.close();
} catch (Exception e) {
e.printStackTrace();
System.err.println("Tried to load texture " + fileName + ", didn't work");
System.exit(-1);
}
return new TextureData(buffer, width, height);
}
//Load textures for GUI
public RawModel loadToVAO(float[] positions, int dimensions) {
int vaoID = createVAO();
this.storeDataInAttributeList(0, dimensions, positions);
unbindVAO();
return new RawModel(vaoID, positions.length / dimensions);
}
}
Solved Thank to Reto Koradi
public void changeVbo(int position, float[] data, int VboId){
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, VboId);
FloatBuffer ArrayData = storeDataInFloatBuffer(data);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER,position * 4, ArrayData);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
The easiest and likely most efficient way is to store the heights (y-values) in a separate VBO, and specify them as a separate vertex attribute.
Then, in your vertex shader code, you can simply reassemble the position from the separate attributes. You might have something like this in your shader code now:
in vec3 pos;
This changes to:
in vec3 posXZ;
in float posY;
...
vec3 pos = vec3(posXZ.x, posY, posXZ.y);
Using a separate VBO for data that changes frequently also allows you to specify the allocation flags accordingly. You can use GL_DYNAMIC_DRAW for the data that changes frequently, GL_STATIC_DRAW for the rest.
Another option would be to use glMapBuffer(). This gives you a CPU pointer to the buffer content, allowing you to modify only the data that you actually want to change. However, you'll have to be careful that you don't introduce undesirable synchronization between CPU and GPU. The glMapBuffer() call might block until the GPU finished all rendering calls using the previous content of the buffer. One common technique is to use multiple copies of the data in a set of buffers, and cycle through them, to minimize synchronization. But if the amount of data is large, that will obviously cause memory usage to increase dramatically.
In your use case, I suspect that you'll have to update the normals as well, since they depend on the height values.
Related
I am drawing a grid of hexagon tiles. I have six different images that I want to use as the fill for these tiles. I use my hexagon class to loop through each point in the tile. The textures are stored in an array list which is then shuffled into a random order. The problem with my script right now is that the same texture is applied to each tile. What am I doing wrong?
public class LWJGLHelloWorld {
public static int SCREEN_WIDTH;
public static int SCREEN_HEIGHT;
public static int WINDOW_WIDTH;
public static int WINDOW_HEIGHT;
public double WIDTH;
public double HEIGHT;
public ArrayList<Hexagon> hexagons = new ArrayList<Hexagon>();
public ArrayList<String> resources = new ArrayList<String>();
public Texture brick;
public Texture stone;
public Texture lumber;
public Texture wool;
public Texture wheat;
public Texture wasteland;
private static enum State {
INTRO, MAIN_MENU, GAME;
}
private State state = State.INTRO;
public LWJGLHelloWorld(){
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double SCREEN_WIDTH = screenSize.getWidth();
double SCREEN_HEIGHT = screenSize.getHeight();
double WIDTH = SCREEN_WIDTH * .85;
double HEIGHT = SCREEN_HEIGHT * .85;
try {
Display.setDisplayMode(new DisplayMode((int)WIDTH, (int)HEIGHT));
Display.setTitle("Hello, LWJGL!");;
Display.create();
} catch (LWJGLException e){
e.printStackTrace();
}
resetResources();
brick = loadTexture("brick");
stone = loadTexture("stone");
lumber = loadTexture("lumber");
//Texture wheat = loadTexture("wheat");
wool = loadTexture("wool");
wasteland = loadTexture("wasteland");
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, WIDTH, HEIGHT, 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
int originX = (int)(Display.getDisplayMode().getWidth() / 2);
int originY = (int)(Display.getDisplayMode().getHeight() / 2);
int radius = (int)(HEIGHT * .1);
int padding = (int)(HEIGHT * .005);
findHexCoords(originX, originY, 5, radius, padding);
while(!Display.isCloseRequested()){
glClear(GL_COLOR_BUFFER_BIT);
for(int h = 0; h < hexagons.size(); h++){
String rsrc = resources.get(h);
bindTexture(rsrc);
glBegin(GL_POLYGON);
Hexagon hex = hexagons.get(h);
for(int p = 0; p < hex.points.length; p++){
Point point = hex.points[p];
glTexCoord2f(point.x, point.y);
glVertex2f(point.x, point.y);
}
glEnd();
}
Display.update();
Display.sync(60);
}
Display.destroy();
}
private void bindTexture(String rsrc){
switch(rsrc){
case "brick":
brick.bind();
break;
case "stone":
stone.bind();
break;
case "lumber":
lumber.bind();
break;
case "wheat":
//wheat.bind();
break;
case "wool":
wool.bind();
break;
case "wasteland":
wasteland.bind();
break;
}
}
private void findHexCoords(int x, int y, int size, int radius, int padding) {
Point origin = new Point(x, y);
double ang30 = Math.toRadians(30);
double xOff = Math.cos(ang30) * (radius + padding);
double yOff = Math.sin(ang30) * (radius + padding);
int half = size / 2;
int i = 0;
for (int row = 0; row < size; row++) {
int cols = size - Math.abs(row - half);
for (int col = 0; col < cols; col++) {
int xLbl = row < half ? col - row : col - half;
int yLbl = row - half;
int centerX = (int) (origin.x + xOff * (col * 2 + 1 - cols));
int centerY = (int) (origin.y + yOff * (row - half) * 3);
Hexagon hex = new Hexagon(centerX, centerY, radius);
System.out.println(centerX+","+centerY);
hexagons.add(hex);
i++;
}
}
}
private Texture loadTexture(String key){
try {
return TextureLoader.getTexture("PNG", new FileInputStream(new File("img/" + key + ".png")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
new LWJGLHelloWorld();
}
public void resetResources(){
resources.clear();
resources.add("Brick");
resources.add("Brick");
resources.add("Brick");
resources.add("Wool");
resources.add("Wool");
resources.add("Wool");
resources.add("Wool");
resources.add("Lumber");
resources.add("Lumber");
resources.add("Lumber");
resources.add("Lumber");
resources.add("Stone");
resources.add("Stone");
resources.add("Stone");
resources.add("Wheat");
resources.add("Wheat");
resources.add("Wheat");
resources.add("Wheat");
long seed = System.nanoTime();
Collections.shuffle(resources, new Random(seed));
int randomIndex = ThreadLocalRandom.current().nextInt(0, 19);
resources.add(randomIndex, "Wasteland");
for(int r = 0; r < resources.size(); r++){
System.out.println(resources.get(r));
}
}
The first letter of the strings that you are adding in resources is uppercase (e.g. Brick).
In the bindTexture switch you are searching strings from resources that have a lowercase first letter (e.g. brick). Therefore the switch should always fail and not able to bind the correct texture.
Either fix the switch or the resources array accordingly.
If I have a geometry with single bone I can render it with animation but if it has 2 bones the vertices do not animated correctly, I load a JSON format exported from blender, as I understand the child bone relative matrix multiplied by its parent absolute matrix then multiplied by it relative matrix inverse then each bone matrix multiplied by the key frame matrix to get the final matrix then send these matrices to the vertex shader.
I set the weight and bone index as attributes with the VBO.
for (int y = 0; y < g.getBones().size(); y++) {
com.thatulborooj.opengl.objects.Bone bone = new com.thatulborooj.opengl.objects.Bone();
Bone gBone = g.getBones().get(y);
float[] t = new float[] { gBone.getPosition().get(0), gBone.getPosition().get(0), gBone.getPosition().get(0) };
float[] r = new float[] { gBone.getRotation().get(0), gBone.getRotation().get(1), gBone.getRotation().get(2), gBone.getRotation().get(3) };
float[] s = gBone.getScale() == null ? new float[] { 1, 1, 1 } : new float[] { gBone.getScale().get(0), gBone.getScale().get(1), gBone.getScale().get(2) };
float[] nMatrix = createMat4(t, r, s);
float[] inverseMatrix = new float[16];
if (gBone.getParent() == -1) {
bone.setBindMatrix(nMatrix);
invertM(inverseMatrix, 0, nMatrix, 0);
float[] fMatrix = new float[16];
multiplyMM(fMatrix, 0, nMatrix, 0, inverseMatrix, 0);
bone.setFinalMatrix(fMatrix);
} else {
float[] pMatrix = mesh.getBones().get(gBone.getParent()).getBindMatrix();
float[] bMatrix = new float[16];
multiplyMM(bMatrix, 0, pMatrix, 0, nMatrix, 0);
bone.setBindMatrix(bMatrix);
invertM(inverseMatrix, 0, bMatrix, 0);
float[] fMatrix = new float[16];
multiplyMM(fMatrix, 0, bMatrix, 0, inverseMatrix, 0);
bone.setFinalMatrix(fMatrix);
}
bone.setName(gBone.getName());
bone.setParent(gBone.getParent());
mesh.getBones().add(bone);
}
}
this is the render code
if (animated) {
if (playing) {
if (curFrame < frames) {
for (int i = 0; i < bones.size(); i++) {
Key key = animations.get(0).getHierarchy().get(i).getKeys().get(curFrame);
setIdentityM(aMatrix, 0);
multiplyMM(aMatrix, 0, bones.get(i).getFinalMatrix(), 0, key.getMatrix(), 0);
for (int q = 0; q < 16; q++) {
bs[i][q] = aMatrix[q];
}
}
curFrame++;
} else
curFrame = 0;
shaderProgram.setBones(bs);
}
}
this is the function which make the matrix from position, quaternion and scale:
public static float[] createMat4(float[] t, float[] r, float[] s) {
float[] mat4 = new float[16];
float[] T = new float[16];
float[] R = quaternionToMatrix(r);
float[] S = new float[16];
setIdentityM(T, 0);
setIdentityM(S, 0);
translateM(T, 0, t[0], t[1], t[2]);
scaleM(S, 0, s[0], s[1], s[2]);
float[] temp = new float[16];
multiplyMM(temp, 0, T, 0, R, 0);
multiplyMM(mat4, 0, temp, 0, S, 0);
return mat4;
}
private static float[] quaternionToMatrix(float[] q) {
float[] m = new float[16];
final float xx = q[0] * q[0];
final float xy = q[0] * q[1];
final float xz = q[0] * q[2];
final float xw = q[0] * q[3];
final float yy = q[1] * q[1];
final float yz = q[1] * q[2];
final float yw = q[1] * q[3];
final float zz = q[2] * q[2];
final float zw = q[2] * q[3];
// Set matrix from quaternion
m[0] = 1 - 2 * (yy + zz);
m[1] = 2 * (xy - zw);
m[2] = 2 * (xz + yw);
m[3] = 0;
m[4] = 2 * (xy + zw);
m[5] = 1 - 2 * (xx + zz);
m[6] = 2 * (yz - xw);
m[7] = 0;
m[8] = 2 * (xz - yw);
m[9] = 2 * (yz + xw);
m[10] = 1 - 2 * (xx + yy);
m[11] = 0;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return m;
}
I set the bones array uniform by this line:
private void setBonesUniforms() {
for (int i = 0; i < bones.length; i++) {
int uBonesLocation = glGetUniformLocation(program, "bones[" + i + "]");
glUniformMatrix4fv(uBonesLocation, 1, false, bones[i], 0);
}
}
finally this is the vertex shader:
vec4 newVertex=vertexPosition;
vec4 newNormal=vertexNormal;
if(animated==1){
int index;
index=int(skinindex.x);
newVertex = (bones[index] * skinweight.x) * vertexPosition;
newNormal = (bones[index] * skinweight.x) * vertexNormal;
index=int(skinindex.y);
newVertex += (bones[index] * skinweight.y) * vertexPosition;
newNormal += (bones[index] * skinweight.y) * vertexNormal;
}
newVertex=vec4(newVertex.xyz, 1.0);
I noticed that changing the child bone matrix with any floats do not change the result.
You can use AssimpLib it is easy to use and supports most formats including collada with skeletal animations
I am trying to make a 2D game with opengl.I made the the class which draw meshes.And i made my own Matrix4f class.I have experience with 3D opengl.
When i pass my matrix4f to the shader it doesn't draw.But when i remove it from multiplying with the position the triangle appears on the screen.I tried my Matrix4f from my 3D Game Engine but it doesn't work as well.
Matrix4f class
package com.game.main.maths;
import java.nio.FloatBuffer;
import com.game.main.util.Util;
public class Matrix4f {
private float[][] m;
public Matrix4f() {
m = new float[4][4];
initIdentity();
}
public Matrix4f initIdentity() {
for (int x = 0; x < 4; x++)
for (int y = 0; y < 4; y++) {
if (x == y)
m[x][y] = 1.0f;
else
m[x][y] = 0;
}
return this;
}
public Matrix4f translate(float x, float y, float z) {
initIdentity();
m[0][3] = x;
m[1][3] = y;
m[2][3] = z;
return this;
}
public Matrix4f translate(Vector3f pos) {
initIdentity();
m[0][3] = pos.getX();
m[1][3] = pos.getY();
m[2][3] = pos.getZ();
return this;
}
public Matrix4f rotate(float angle) {
initIdentity();
float rad = (float) Math.toRadians(angle);
float cos = (float) Math.cos(rad);
float sin = (float) Math.sin(rad);
m[0][0] = cos;
m[1][0] = sin;
m[0][1] = -sin;
m[1][1] = cos;
return this;
}
public Matrix4f initOrthographic(float left, float right, float bottom,
float top, float near, float far) {
initIdentity();
m[0][0] = 2.0f / (right - left);
m[1][1] = 2.0f / (top - bottom);
m[2][2] = 2.0f / (near - far);
m[0][3] = (left + right) / (left - right);
m[1][3] = (bottom + top) / (bottom - top);
m[2][3] = (near + far) / (far - near);
return this;
}
public Matrix4f mul(Matrix4f matrix) {
Matrix4f result = new Matrix4f();
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
float sum = 0.0f;
for (int index = 0; index < 4; index++)
sum += m[index][y] * matrix.get(x, index);
result.set(x, y, sum);
}
}
return result;
}
public float[][] getM() {
return m;
}
public float get(int x, int y) {
return m[x][y];
}
public void setM(float[][] m) {
this.m = m;
}
public void set(int x, int y, float value) {
m[x][y] = value;
}
public FloatBuffer toFloatBuffer() {
return Util.createFlippedBufferBuffer(this);
}
}
Shader class function
public void setUniform(String uniformName, Matrix4f matrix){
int uniformLocation = getUniformLocation(uniformName);
glUniformMatrix4(uniformLocation, false, matrix.toFloatBuffer());
}
Util class
package com.game.main.util;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import com.game.main.graphics.Vertex;
import com.game.main.maths.Matrix4f;
public class Util {
public static FloatBuffer createFlippedBuffer(float[] data){
FloatBuffer buffer = ByteBuffer.allocateDirect(data.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
buffer.put(data).flip();
return buffer;
}
public static IntBuffer createFlippedBuffer(int[] data){
IntBuffer buffer = ByteBuffer.allocateDirect(data.length * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
buffer.put(data).flip();
return buffer;
}
public static IntBuffer createIntBuffer(int size){
IntBuffer buffer = ByteBuffer.allocateDirect(size * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
return buffer;
}
public static FloatBuffer createFloatBuffer(int size){
FloatBuffer buffer = ByteBuffer.allocateDirect(size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
return buffer;
}
public static FloatBuffer createFlippedBufferBuffer(Matrix4f matrix){
FloatBuffer buffer = createFloatBuffer(4 * 4);
for(int x = 0; x < 4; x++)
for(int y = 0; y < 4; y++)
buffer.put(matrix.get(x, y));
buffer.flip();
return buffer;
}
public static FloatBuffer createFlippedBuffer(Vertex[] vertecies){
FloatBuffer buffer = createFloatBuffer(vertecies.length * Vertex.SIZE);
for(int i = 0; i < vertecies.length; i++){
buffer.put(vertecies[i].getPos().getX());
buffer.put(vertecies[i].getPos().getY());
buffer.put(vertecies[i].getPos().getZ());
buffer.put(vertecies[i].getTexCoord().getX());
buffer.put(vertecies[i].getTexCoord().getY());
}
buffer.flip();
return buffer;
}
}
Initializing the matrix uniform
Matrix4f matrix = new Matrix4f().initOrthographic(0, 800, 0, 600, -1.0f, 1.0f);
Shader.Object.setUniform("mat", matrix);
Vertex Shader
#version 330
layout(location = 0) in vec3 pos;
uniform mat4 mat;
void main(){
gl_Position = mat * vec4(pos, 1.0f);
}
Mesh class
package com.game.main.graphics;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import com.game.main.util.Util;
public class Mesh {
private int vbo, ibo, size;
public Mesh(Vertex[] vertecies, int[] indices){
vbo = glGenBuffers();
ibo = glGenBuffers();
size = indices.length;
createMesh(vertecies, indices);
}
private void createMesh(Vertex[] vertecies, int[] indices){
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, Util.createFlippedBuffer(vertecies), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * 4, 0);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * 4, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, Util.createFlippedBuffer(indices), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
public void bind(){
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
}
public void unbind(){
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
public void drawWithBinding()
{
bind();
glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0);
unbind();
}
public void draw(){
glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0);
}
public void destroy(){
glDeleteBuffers(ibo);
glDeleteBuffers(vbo);
}
}
Vertex class
package com.game.main.graphics;
import com.game.main.maths.Vector2f;
import com.game.main.maths.Vector3f;
public class Vertex {
private Vector3f pos = new Vector3f();
private Vector2f texCoord = new Vector2f();
public static final int SIZE = 5;
public Vertex(Vector3f pos){
this(pos, new Vector2f());
}
public Vertex(Vector3f pos, Vector2f texCoord){
this.pos = pos;
this.texCoord = texCoord;
}
public Vector3f getPos() {
return pos;
}
public void setPos(Vector3f pos) {
this.pos = pos;
}
public Vector2f getTexCoord() {
return texCoord;
}
public void setTexCoord(Vector2f texCoord) {
this.texCoord = texCoord;
}
}
transpose code
public Matrix4f transpose() {
float m00 = get(0, 0);
float m01 = get(1, 0);
float m02 = get(2, 0);
float m03 = get(3, 0);
float m10 = get(0, 1);
float m11 = get(1, 1);
float m12 = get(2, 1);
float m13 = get(3, 1);
float m20 = get(0, 2);
float m21 = get(1, 2);
float m22 = get(2, 2);
float m23 = get(3, 2);
float m30 = get(0, 3);
float m31 = get(1, 3);
float m32 = get(2, 3);
float m33 = get(3, 3);
set(0, 0, m00);
set(0, 1, m01);
set(0, 2, m02);
set(0, 3, m03);
set(1, 0, m10);
set(1, 1, m11);
set(1, 2, m12);
set(1, 3, m13);
set(2, 0, m20);
set(2, 1, m21);
set(2, 2, m22);
set(2, 3, m23);
set(3, 0, m30);
set(3, 1, m31);
set(3, 2, m32);
set(3, 3, m33);
return this;
}
Game class
package com.game.main;
import static org.lwjgl.opengl.GL11.*;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import com.game.main.graphics.Mesh;
import com.game.main.graphics.Shader;
import com.game.main.graphics.Vertex;
import com.game.main.maths.Matrix4f;
import com.game.main.maths.Vector3f;
public class Game{
private boolean running = false;
private Mesh mesh;
public Game(int width, int height){
try {
Display.setDisplayMode(new DisplayMode(800, 600));
Display.setTitle("2D Game");
Display.create();
} catch (LWJGLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Matrix4f matrix = new Matrix4f().initOrthographic(-1, 1, -1, 1, -1.0f, 1.0f);
Shader.Object.setUniform("mat", matrix);
Vertex[] vertecies = new Vertex[]{
new Vertex(new Vector3f(0.5f, 0.5f, 0)),
new Vertex(new Vector3f(0.5f, 0,0)),
new Vertex(new Vector3f(0, 0.5f,0)),
new Vertex(new Vector3f(0.5f, 0,0))
};
/*float[] vertecies = new float[]{
-10f, -10f * 9.0f / 16.0f, 1,
-10f, 10f * 9.0f / 16.0f,1,
0, 10f * 9.0f / 1.0f,1,
0, -10f * 9.0f / 6.0f,1
};*/
int[] ind = new int[]{
0, 1, 2,
2, 3, 0
};
mesh = new Mesh(vertecies, ind);
}
private void init(){
glEnable(GL_DEPTH_TEST);
}
public void start(){
if(running)
return;
running = true;
gameLoop();
}
public void stop(){
if(!running)
return;
running = false;
}
private void gameLoop(){
init();
long timer = System.currentTimeMillis();
long lastTime = System.nanoTime();
double delta = 0;
int frames = 0, updates = 0;
while(running){
long now = System.nanoTime();
delta += (now - lastTime) / (1000000000.0 / 60.0);
lastTime = now;
while(delta >= 1){
if(Display.isCloseRequested())
stop();
update();
updates++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer >= 1000){
timer += 1000;
Display.setTitle("Frames: "+frames+" Updates: "+updates);
frames = updates = 0;
}
}
destroy();
}
private void update(){
}
private void render(){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Shader.Object.bind();
mesh.drawWithBinding();
Shader.Object.unbind();
Display.update();
}
private void destroy(){
mesh.destroy();
Display.destroy();
}
}
It seems the problem is in setting the matrix uniform. OpenGL is a state machine and doesn't remember uniform values. More importantly, glUniformMatrix4 must be called while the shader is in use.
Currently, the matrix is set only at initialization (in the Game constructor), when it needs to be in render().
Shader.Object.bind(); //glUseProgram(programHandle)
Shader.Object.setUniform(...) //glUniformMatrix4
mesh.drawWithBinding(); //glDrawElements
Shader.Object.unbind(); //glUseProgram(0), but not necessary unless you want fixed function rendering
With mat not being set it'd probably be a zero matrix that was stopping things from drawing.
In addition to the above, there are two other potential issues:
Take a look in the bottom left of your screen and see if that pixel is on. I suspect everything's working, just really small. If the following works:
gl_Position = vec4(pos, 1.0f);
which is essentially a multiply by the identity matrix and nearly ortho(-1,1,-1,1,-1,1) (except the ortho call gives mat[2][2]=-1), then I'm guessing your mesh is around the size of a unit cube.
Introducing mat = initOrthographic(0, 800, 0, 600, -1.0f, 1.0f) looks like it's set up for a scene where vertices are given in pixels. The origin will be bottom left and if the mesh fits in a unit cube will draw to at most one pixel.
See #Jerem's answer about row/column major matrices.
It looks like your matrix is row major. You need to transpose it before sending it to the shader.
Note that there is a boolean parameter called transpose in glUniformMatrix4f but you can't use it in opengl es - the doc says it has to be false - you have to transpose it yourself.
I'm having a set of vertices (X,Y) of an irregular polygon, some having 4 vertices and some more than 4. I'm creating an array of vertices for drawing from the set of vertices I have using the getTransformedVertices() method. The indices and UV for the texture are unknown so I had to calculate tem using Triangulate() method below. I've setup the color and texture in the setColor() and setImage() methods.
However, the polygons having more than 4 vertices are not rendered properly. I've been trying for weeks now and almost the searched half of the internet. This is what I could come up with. The polygons with 4 vertices and the outlines are working properly. But I couldn't get the textures displayed properly on the polygon. Please help
public class BoothRectangle
{
float angle;
float scale;
RectF base;
PointF translation;
int textureId;
int positionBufferId;
int textureBufferId;
PointF[] corners;
float[] verts;
// Geometric variables
public float vertices[];
public float colors[];
public short indices[];
public float uvs[];
public FloatBuffer vertexBuffer;
public ShortBuffer drawListBuffer;
public FloatBuffer colorBuffer;
public FloatBuffer uvBuffer;
TextPaint textPaint = new TextPaint();
String title;
public BoothRectangle(PointF[] corners, int textureId, float[] colors, String title)
{
// Initialise our intital size around the 0,0 point
base = new RectF(corners[1].x, corners[3].y, corners[0].x, corners[1].y);
this.corners = corners;
this.title = title;
// Offset translation
translation = new PointF(0f,0f);
// Initial Size
scale = 1f;
// We start in our inital angle
angle = 0f;
this.textureId = textureId;
this.colors = colors;
}
public void setColor(float[] topColor){
List<Float> colorsList = new ArrayList<Float>();
for(PointF point : corners){
colorsList.add(topColor[0] / 255);
colorsList.add(topColor[1] / 255);
colorsList.add(topColor[2] / 255);
colorsList.add(1f);
}
int i = 0;
float[] colors = new float[colorsList.size()];
for (Float f : colorsList) {
colors[i++] = (f != null ? f : Float.NaN);
}
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());
colorBuffer = cbb.asFloatBuffer();
colorBuffer.put(colors);
colorBuffer.position(0);
int[] buffers = new int[1];
GLES11.glGenBuffers(1, buffers, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, buffers[0]);
GLES11.glBufferData(GLES11.GL_ARRAY_BUFFER, 4 * colors.length, colorBuffer, GLES11.GL_STATIC_DRAW);
textureBufferId = buffers[0];
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
}
public float[] getTransformedVertices()
{
float z;
List<Float> finalVertices = new ArrayList<Float>();
if(textureId == 0)
z = 0.5f;
else
z = 2.0f;
finalVertices.clear();
for(PointF point : corners){
finalVertices.add(point.x);
finalVertices.add(point.y);
finalVertices.add(0.0f);
}
int i = 0;
float[] verticesArray = new float[finalVertices.size()];
for (Float f : finalVertices) {
verticesArray[i++] = (f != null ? f : Float.NaN);
}
return verticesArray;
}
public void setImage()
{
uvs = new float[] {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
// The texture buffer
ByteBuffer bb = ByteBuffer.allocateDirect(uvs.length * 4);
bb.order(ByteOrder.nativeOrder());
uvBuffer = bb.asFloatBuffer();
uvBuffer.put(uvs);
uvBuffer.position(0);
int[] buffers = new int[1];
GLES11.glGenBuffers(1, buffers, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, buffers[0]);
GLES11.glBufferData(GLES11.GL_ARRAY_BUFFER, 4 * uvs.length, uvBuffer, GLES11.GL_STATIC_DRAW);
textureBufferId = buffers[0];
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
}
public void initBooth()
{
vertices = this.getTransformedVertices();
indices = PolygonTriangulation.Process(vertices);
if(indices==null){
Log.d("PolygonTriangulation",title + " - failed");
if(this.corners.length == 4){
indices = new short[] {2, 1, 0, 2, 0, 3};
}else{
indices = new short[corners.length];
for(int i=0;i<corners.length;i++){
indices[i] = (short) i;
}
}
}
// The vertex buffer.
ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
ByteBuffer dlb = ByteBuffer.allocateDirect(indices.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(indices);
drawListBuffer.position(0);
int[] buffers = new int[1];
GLES11.glGenBuffers(1, buffers, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, buffers[0]);
GLES11.glBufferData(GLES11.GL_ARRAY_BUFFER, 4 * vertices.length, vertexBuffer, GLES11.GL_STATIC_DRAW);
positionBufferId = buffers[0];
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
if(this.textureId !=0){
setImage();
} else {
setColor(colors);
}
}
public void Render(GL10 gl){
if(textureId == 0){
GLES11.glPushMatrix();
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, positionBufferId);
GLES11.glEnableClientState(GL10.GL_VERTEX_ARRAY);
GLES11.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
GLES11.glFrontFace(GL10.GL_CW);
GLES11.glColor4f(0.8f, 0.8f, 0.8f, 0.8f);
GLES11.glLineWidth(3.0f);
GLES11.glDrawArrays(GL10.GL_LINE_LOOP, 0, corners.length);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, textureBufferId);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
GLES11.glColorPointer(4, GL10.GL_FLOAT, 0, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
GLES11.glDrawElements(GL10.GL_TRIANGLES, indices.length,
GL10.GL_UNSIGNED_SHORT, drawListBuffer);
GLES11.glDisableClientState(GL10.GL_VERTEX_ARRAY);
GLES11.glDisableClientState(GL10.GL_COLOR_ARRAY);
GLES11.glPopMatrix();
} else {
GLES11.glPushMatrix();
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, positionBufferId);
GLES11.glEnableClientState(GL10.GL_VERTEX_ARRAY);
GLES11.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, textureBufferId);
GLES11.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
GLES11.glTexCoordPointer(2, GL10.GL_FLOAT, 0, 0);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
GLES11.glEnable(GL10.GL_TEXTURE_2D);
GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
GLES11.glEnable(GL10.GL_BLEND);
GLES11.glFrontFace(GL10.GL_CW);
GLES11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
int error = gl.glGetError();
if (error != GL10.GL_NO_ERROR)
{
Log.e("OPENGL", "GL Texture Load Error: " + GLU.gluErrorString(error));
}
GLES11.glDrawElements(GL10.GL_TRIANGLES, indices.length,
GL10.GL_UNSIGNED_SHORT, drawListBuffer);
GLES11.glDisable(GL10.GL_BLEND);
GLES11.glDisableClientState(GL10.GL_VERTEX_ARRAY);
GLES11.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
GLES11.glDisable(GL10.GL_TEXTURE_2D);
GLES11.glPopMatrix();
}
}
}
PolygonTriangulation.java - got from AssetFX - http://www.experts-exchange.com/Programming/Languages/Java/Q_27882746.html
public class PolygonTriangulation {
static final float EPSILON=0.0000000001f;
static public float Area(float[] contour) {
int n = contour.length;
float A = 0.0f;
for(int p = n - 3, q = 0; q < n; p=q, q+=3)
{
A += contour[p] * contour[q+1] - contour[q] * contour[p+1];
}
return A * 0.5f;
}
static public boolean InsideTriangle(float Ax,float Ay,float Bx,float By,float Cx,float Cy,float Px,float Py){
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
float cCROSSap, bCROSScp, aCROSSbp;
ax = Cx - Bx; ay = Cy - By;
bx = Ax - Cx; by = Ay - Cy;
cx = Bx - Ax; cy = By - Ay;
apx= Px - Ax; apy= Py - Ay;
bpx= Px - Bx; bpy= Py - By;
cpx= Px - Cx; cpy= Py - Cy;
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}
static public boolean Snip(float[] contour, int u, int v, int w, int n, int[] V) {
int p;
float Ax, Ay, Bx, By, Cx, Cy, Px, Py;
Ax = contour[V[u]];
Ay = contour[V[u+1]];
Bx = contour[V[v]];
By = contour[V[v+1]];
Cx = contour[V[w]];
Cy = contour[V[w+1]];
if ( EPSILON > (((Bx-Ax)*(Cy-Ay)) - ((By-Ay)*(Cx-Ax))) ){
return false;
}
for (p = 0; p < n; p++)
{
if( (p == u/2) || (p == v/2) || (p == w/2) ){
continue;
}
Px = contour[V[p*2]];
Py = contour[V[(p*2)+1]];
if (InsideTriangle(Ax,Ay,Bx,By,Cx,Cy,Px,Py)){
return false;
}
}
return true;
}
//Brings in 3D vertex float array but processes it as 2D only.
static public short[] Process(float[] contour) {
//Pre-return list
ArrayList<Integer> vertexArray = new ArrayList<Integer>();
/* allocate and initialize list of Vertices in polygon */
int n = contour.length;
if ( n/3 < 3 )
return null;
//The (n/3)*2 occurs as we are removing the z coordinate from the mix
int[] V = new int[(n/3)*2];
/* we want a counter-clockwise polygon in V */
if (0.0f < Area(contour)){
for (int s = 0, v = 0; v < n-1; s+=2, v += 3){
V[s] = v;
V[s + 1] = v + 1;
}
}
else{
for(int s = 0, v = 0; v < n-1; s += 2, v += 3){
V[s] = (n - 1) - (v + 2);
V[s + 1] = (n - 1) - (v + 1);
}
}
int nv = n/3;
/* remove nv-2 Vertices, creating 1 triangle every time */
int count = 2 * nv; /* error detection */
for(int v = nv - 1; nv > 2;)
{
/* if we loop, it is probably a non-simple polygon */
if (0 >= (count--))
{
//** Triangulate: ERROR - probable bad polygon!
Log.e("PolygonTriangulation","Invalid Polygon");
return null;
}
/* three consecutive vertices in current polygon, <u,v,w> */
int u = v;
if (nv <= u)
u = 0; /* previous */
v = u + 1;
if (nv <= v)
v = 0; /* new v */
int w = v + 1;
if (nv <= w)
w = 0; /* next */
if (Snip(contour, u*2, v*2, w*2, nv, V))
{
vertexArray.add(V[u*2]/3);
vertexArray.add(V[v*2]/3);
vertexArray.add(V[w*2]/3);
// remove v from remaining polygon
for(int s = v * 2, t = (v * 2) + 2; t < (nv * 2); s += 2, t += 2){
V[s] = V[t];
V[s+1] = V[t+1];
}
nv--;
// reset error detection counter
count = 2 * nv;
}
}
//Convert ArrayList into short array
short[] index = new short[vertexArray.size()];
for(int i = 0; i < vertexArray.size(); i++){
index[i] = vertexArray.get(i).shortValue();
}
return index;
}
}
I'm in major need for help. I am working on a 2d game in libgdx. All is working well, and today I decided to make a small 2d particle system to make fireworks. I remembered I had recently accomplished this task using shaders in 3d in the book OpenGL ES 2.0 for Android: A Quick Start Guide. So I copied most of the code and started modifying some things so it would work with Libgdx and in 2d. How this particle editor works is there is one big float array that holds 8 things per particle: 2 positions, x and y; 3 colors, rgb; 2 directions, x and y; 1 start time. Now this repeats for every particle allowed in the array. I set the max particles to 10,000, so 80,000 floats in this array, meaning 320,000 bytes. Anyways, now I have all of these attributes. Now I put these attributes into the shader program. The shader program uses these to move the particles by the direction, fade the color by time, and apply gravity. So anyways, it worked uses pure OpenGL, but I wanted to use the LibGDX framework. I tried to keep as much as the old code as I could. I thought what I did would work, but it won't show anything. Here is the code...
ParticleFirework.java:
public class ParticleFirework {
private final Vector2 position;
private final Vector2 direction;
private final int color;
private final float angleVariance;
private final float speedVariance;
private final Random random = new Random();
private float[] rotationMatrix = new float[16];
private float[] directionVector = new float[4];
private float[] resultVector = new float[4];
public ParticleFirework(Vector2 position, Vector2 direction, int color,
float angleVarianceInDegrees, float speedVariance) {
this.position = position;
this.direction = direction;
this.color = color;
this.angleVariance = angleVarianceInDegrees;
this.speedVariance = speedVariance;
directionVector[0] = direction.x;
directionVector[1] = direction.y;
directionVector[2] = 0;
}
public void addParticles(ParticleSystem particleSystem, float currentTime,
int count) {
for (int i = 0; i < count; i++) {
setRotateEulerM(rotationMatrix, 0, (random.nextFloat() - 0.5f)
* angleVariance, (random.nextFloat() - 0.5f)
* angleVariance, (random.nextFloat() - 0.5f)
* angleVariance);
multiplyMV(resultVector, 0, rotationMatrix, 0, directionVector, 0);
float speedAdjustment = 1f + random.nextFloat() * speedVariance;
Vector2 thisDirection = new Vector2(resultVector[0]
* speedAdjustment, resultVector[1] * speedAdjustment);
particleSystem.addParticle(position, color, thisDirection,
currentTime);
}
}
}
ParticleSystem.java:
public class ParticleSystem {
private static final int PARTICLE_POSITION_COUNT = 2;
private static final int PARTICLE_COLOR_COUNT = 3;
private static final int PARTICLE_DIRECTION_COUNT = 2;
private static final int PARTICLE_START_TIME_COUNT = 1;
private static final int BYTES_IN_A_FLOAT = 4;
private static final int TOTAL_COUNT = PARTICLE_POSITION_COUNT
+ PARTICLE_COLOR_COUNT + PARTICLE_DIRECTION_COUNT
+ PARTICLE_START_TIME_COUNT;
private static final int STRIDE = TOTAL_COUNT * BYTES_IN_A_FLOAT;
private final float[] particles;
private final int maxParticleCount;
private int currentParticleCount;
private int nextParticle;
private FloatBuffer particleFloatBuffer;
public ParticleSystem(int maxParticleCount) {
particles = new float[maxParticleCount * TOTAL_COUNT];
this.maxParticleCount = maxParticleCount;
particleFloatBuffer = ByteBuffer
.allocateDirect(particles.length * BYTES_IN_A_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer().put(particles);
}
public void addParticle(Vector2 position, int color, Vector2 direction,
float particleStartTime) {
final int particleOffset = nextParticle * TOTAL_COUNT;
int currentOffset = particleOffset;
nextParticle++;
if (currentParticleCount < maxParticleCount) {
currentParticleCount++;
}
if (nextParticle == maxParticleCount) {
nextParticle = 0;
}
particles[currentOffset++] = position.x;
particles[currentOffset++] = position.y;
particles[currentOffset++] = Color.red(color) / 255f;
particles[currentOffset++] = Color.green(color) / 255f;
particles[currentOffset++] = Color.blue(color) / 255f;
particles[currentOffset++] = direction.x;
particles[currentOffset++] = direction.y;
particles[currentOffset++] = particleStartTime;
particleFloatBuffer.position(particleOffset);
particleFloatBuffer.put(particles, particleOffset, TOTAL_COUNT);
particleFloatBuffer.position(0);
}
public void bindData(ShaderProgram particleProgram) {
int dataOffset = 0;
particleProgram.setVertexAttribute("a_Position",
PARTICLE_POSITION_COUNT, GL20.GL_FLOAT, false, STRIDE,
dataOffset);
dataOffset += PARTICLE_POSITION_COUNT;
particleProgram.setVertexAttribute("a_Color",
PARTICLE_COLOR_COUNT, GL20.GL_FLOAT, false, STRIDE,
dataOffset);
dataOffset += PARTICLE_COLOR_COUNT;
particleProgram.setVertexAttribute("a_DirectionVector",
PARTICLE_DIRECTION_COUNT, GL20.GL_FLOAT, false, STRIDE,
dataOffset);
dataOffset += PARTICLE_DIRECTION_COUNT;
particleProgram.setVertexAttribute("a_ParticleStartTime",
PARTICLE_START_TIME_COUNT, GL20.GL_FLOAT, false, STRIDE,
dataOffset);
}
public void draw() {
glDrawArrays(GL_POINTS, 0, currentParticleCount);
}
}
ParticleGame.java:
public class ParticlesGame extends ApplicationAdapter {
SpriteBatch batch;
ShaderProgram particleProgram;
OrthographicCamera camera;
Texture img;
private ParticleSystem particleSystem;
private ParticleFirework firework;
private long globalStartTime;
#Override
public void create() {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
globalStartTime = System.nanoTime();
final Vector2 particleDirection = new Vector2(0f, 0.5f);
final float angleVarianceInDegrees = 5f;
final float speedVariance = 1f;
particleProgram = new ShaderProgram("shaders/particle_shader.vs",
"shaders/particle_shader.fs");
particleSystem = new ParticleSystem(10000);
firework = new ParticleFirework(new Vector2(0f, 0f), particleDirection,
Color.rgb(255, 50, 5), angleVarianceInDegrees, speedVariance);
}
#Override
public void render() {
float currentTime = (System.nanoTime() - globalStartTime) / 1000000000f;
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
firework.addParticles(particleSystem, currentTime, 5);
particleProgram.begin();
FloatBuffer matrixBuffer = ByteBuffer
.allocateDirect(camera.combined.getValues().length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(camera.combined.getValues());
/** Set the uniforms **/
particleProgram.setUniformMatrix4fv("u_matrix", matrixBuffer, 1, false);
particleProgram.setUniform1fv("u_Time", new float[] {currentTime}, 0, 1);
/** Bind data **/
particleSystem.bindData(particleProgram);
particleSystem.draw();
}
}
particle_shader.vs
uniform mat4 u_Matrix;
uniform float u_Time;
attribute vec3 a_Position;
attribute vec3 a_Color;
attribute vec3 a_DirectionVector;
attribute float a_ParticleStartTime;
varying vec3 v_Color;
varying float v_ElapsedTime;
void main()
{
v_Color = a_Color;
v_ElapsedTime = u_Time - a_ParticleStartTime;
float gravityFactor = v_ElapsedTime * v_ElapsedTime / 8.0;
vec3 currentPosition = a_Position + (a_DirectionVector * v_ElapsedTime);
currentPosition.y -= gravityFactor;
gl_Position = u_Matrix * vec4(currentPosition, 1.0);
gl_PointSize = 25.0;
}
I