so I've been playing around with the LWJGL and made a 3D space that creates a number of cubes with random velocities and colours, originally using two VBOs (one each for location and colour).
I'm trying to make it work with a single, interleaved VBO, but my attempts so far result in the cubes being draw with 'flashing' colours and multiple colours per cube when they should be single, solid colours. It looks like it's an error to do with the starting position and stride for the vertices/colours but changing them around doesn't appear to help, or causes some very strange effects.
Here's the code from the class creating the VBO (there shouldn't be any errors elsewhere since it works fine with the non-interleaved version and they contain equivalent methods):
package test3D.first.main;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import org.lwjgl.BufferUtils;
import test3D.first.entities.*;
import test3D.first.main.threads.Updater;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
public class InterleavedDrawer {
private int vboHandle;
private static FloatBuffer drawData;
final static int vertexSize = 3;
final static int colorSize = 3;
final static int normalSize = 3;
private static int vertexQuantity;
public InterleavedDrawer() {
initialise();
}
private void initialise() {
drawData = BufferUtils.createFloatBuffer(0);
vboHandle = glGenBuffers();
updateBuffers(Updater.objects);
}
public void draw() {
glBindBuffer(GL_ARRAY_BUFFER, vboHandle);
glVertexPointer(vertexSize, GL_FLOAT, 1, 0L);
glColorPointer(colorSize, GL_FLOAT, 1, 4L);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLES, 0, vertexQuantity);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
public void update() {
for (int i = 0; i < Updater.getObjectQuantity(); i++) {
drawData.put(Updater.objects.get(i).getVertices());
drawData.put(Updater.objects.get(i).getColors());
}
drawData.flip();
glBindBuffer(GL_ARRAY_BUFFER, vboHandle);
glBufferData(GL_ARRAY_BUFFER, drawData, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
public void clear() {
drawData.clear();
}
private void updateBuffers(ArrayList<AbstractEntity> objects) {
vertexQuantity = 0;
drawData.clear();
for (int i = 0; i < objects.size(); i++) {
vertexQuantity += objects.get(i).getVertexQuantity();
}
drawData = BufferUtils.createFloatBuffer(vertexSize * vertexQuantity + colorSize * vertexQuantity);
}
}
As I said, I suspect the error comes from the lines:
glVertexPointer(vboHandle, GL_FLOAT, 1, 0L);
glColorPointer(vboHandle, GL_FLOAT, 1, 3L);
Leaving the values as they are causes the odd flashing effects, changing the long value in the glColorPointer() to 4 strangely creates 'regions' within the cubes that are colours corresponding to (0, 0, 0), (1, 0, 0), (0, 1, 0) etc., which makes no sense whatsoever since the colours are entirely random floats, and any other values I've tried don't seem to do anything other than the first effect. Am I misusing the values here or something else? Any help at all would be very much appreciated, thanks in advance :)
Stride & offset are specified in bytes. So the stride should be 6 floats * 4 bytes = 24 and the offset 0 (vertex) and 12 (color) respectively.
Also there's a mistake in update(). Right now vertex and color data are interleaved like this (assuming a single triangle per object):
( object 1 ) ( object 2 )
V1V2V3 C1C2C3 V4V5V6 C4C5C6 ...
Where V# is a vector (x,y,z) and C# is a color (r,g,b).
OpenGL expects them in this order:
( object 1 ) ( object 2 )
V1C1 V2C2 V3C3 V4C4 V5C5 V6C6 ...
So the body of the update loop should be changed to something like this (not tested)
float[] vertData = Update.object.get(i).getVertices();
float[] colorData = Update.object.get(i).getColors();
for(int j = 0; j < vertData.length; j += 3) {
drawData.put(vertData, j, 3)
drawData.put(colorData, j, 3)
}
More information
Related
I am trying to implement frustum culling in my 3D Game currently and it has worked efficiently with the entities because they have a bounding box (AABB) and its easier to check a box against the frustum. On saying that, how would I cull the terrain? (it physically cannot have a AABB or sphere)
The frustum class (I use the inbuilt JOML one):
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import engine.Terrians.Terrain;
import engine.maths.Matrices;
import engine.maths.Vector3f;
import engine.objects.Camera;
import engine.physics.AABB;
public class FrustumG {
private final Matrix4f projectionViewMatrix;
private FrustumIntersection frustumInt;
public FrustumG() {
projectionViewMatrix = new Matrix4f().identity();
frustumInt = new FrustumIntersection();
}
public void update(Matrix4f projectionMatrix, Matrix4f viewMatrix) {
projectionViewMatrix.set(projectionMatrix);
projectionViewMatrix.mul(viewMatrix);
frustumInt.set(projectionViewMatrix);
}
public boolean intersectsAABB(AABB aabb) {
return frustumInt.testAab(aabb.getWorldMinX(), aabb.getWorldMinY(), aabb.getWorldMinZ(),
aabb.getWorldMaxX(), aabb.getWorldMaxY(), aabb.getWorldMaxZ());
}
public boolean intersectsPoint(Vector3f point) {
return frustumInt.testPoint(point.getX(), point.getY(), point.getZ());
}
}
My Mesh Class stores vertices information for the Terrain. I do not want to edit and update the vertices VBO every frame, as I have googled that it can affect the performance of the game (and I would also have to edit the indices list, and loop through the two lists every frame). I saw some websites saying to use GL_DYNAMIC_DRAW instead of GL_STATIC_DRAW, but I did not understand it.
Here is the Mesh Class:
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
public class Mesh {
private float[] verticesData;
private int[] indicesData;
private float[] textureData;
private float[] normalsData;
private int vao, pbo, ibo, cbo, tbo, nbo;
private Texture texture;
public Mesh(float[] verticesArray, int[] indices, float[] normalsArray, float[] texturesArray) {
this.verticesData = verticesArray;
this.indicesData = indices;
this.textureData = texturesArray;
this.normalsData = normalsArray;
}
public Mesh(float[] positions, int dimensions) {
this.verticesData = positions;
vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);
FloatBuffer positionBuffer = MemoryUtil.memAllocFloat(positions.length);
positionBuffer.put(positions).flip();
pbo = storeData(positionBuffer, 0, dimensions);
}
public void createTextures(String filepath) {
Texture texture = new Texture(filepath);
texture.create();
this.texture = texture;
}
public int loadCubeMap(String[] textureFiles, String textureMainSystemPath) {
int texID = GL11.glGenTextures();
GL30.glActiveTexture(GL30.GL_TEXTURE0);
GL30.glBindTexture(GL30.GL_TEXTURE_CUBE_MAP, texID);
for(int i = 0; i < textureFiles.length; i++) {
Texture.createSkybox(textureMainSystemPath + textureFiles[i] + ".png", i);
}
GL30.glTexParameteri(GL30.GL_TEXTURE_CUBE_MAP, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR);
GL30.glTexParameteri(GL30.GL_TEXTURE_CUBE_MAP, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR);
return texID;
}
public void createMeshes() {
vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vao);
FloatBuffer positionBuffer = MemoryUtil.memAllocFloat(verticesData.length);
positionBuffer.put(verticesData).flip();
pbo = storeData(positionBuffer, 0, 3);
/**FloatBuffer colorBuffer = MemoryUtil.memAllocFloat(vertices.length * 3);
float[] colorData = new float[vertices.length * 3];
for (int i = 0; i < vertices.length; i++) {
colorData[i * 3] = vertices[i].getColor().getX();
colorData[i * 3 + 1] = vertices[i].getColor().getY();
colorData[i * 3 + 2] = vertices[i].getColor().getZ();
}
colorBuffer.put(colorData).flip();
cbo = storeData(colorBuffer, 1, 3);**/
FloatBuffer textureBuffer = MemoryUtil.memAllocFloat(verticesData.length);
textureBuffer.put(textureData).flip();
tbo = storeData(textureBuffer, 1, 2);
FloatBuffer normalBuffer = MemoryUtil.memAllocFloat(verticesData.length);
normalBuffer.put(normalsData).flip();
nbo = storeData(normalBuffer, 2, 3);
IntBuffer indicesBuffer = MemoryUtil.memAllocInt(indicesData.length);
indicesBuffer.put(indicesData).flip();
ibo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ibo);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
}
private int storeData(FloatBuffer buffer, int index, int size) {
int bufferID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(index, size, GL11.GL_FLOAT, false, 0, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return bufferID;
}
public void destroyBuffers() {
GL15.glDeleteBuffers(pbo);
GL15.glDeleteBuffers(cbo);
GL15.glDeleteBuffers(ibo);
GL15.glDeleteBuffers(tbo);
GL15.glDeleteBuffers(nbo);
GL30.glDeleteVertexArrays(vao);
}
public float[] getVertices() {
return verticesData;
}
public float[] getPositions2D() {
return verticesData;
}
public int[] getIndices() {
return indicesData;
}
public int getVAO() {
return vao;
}
public int getPBO() {
return pbo;
}
public int getCBO() {
return cbo;
}
public int getIBO() {
return ibo;
}
public int getTBO() {
return tbo;
}
public int getNBO() {
return nbo;
}
public Texture getTexture() {
return texture;
}
}
Is there a more efficient way to frustum cull the vertices / triangles of the terrain?
One way to determine what section of your terrain should be culled is to use a quadtree (for a heightmap) or an octree (for a voxel map). Basically, you divide your terrain into little chunks that then get divided further accordingly. You can then test if these chunks are in your viewing frustum and cull them if necessary. This technique was already discussed in great detail:
Efficient (and well explained) implementation of a Quadtree for 2D collision detection
https://www.rastertek.com/tertut05.html
https://gamedev.stackexchange.com/questions/15697/quadtree-terrain-splitting-i-dont-get-it
I saw some websites saying to use GL_DYNAMIC_DRAW instead of GL_STATIC_DRAW, but I did not understand it.
These are usage hints to OpenGL on how the data will be accessed so the implementation has the ability to apply certain optimizations on how to store/use it.
usage is a hint to the GL implementation as to how a buffer object's data store will be accessed. This enables the GL implementation to make more intelligent decisions that may significantly impact buffer object performance. (https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBufferData.xhtml)
Please note that these are only indications, no restrictions:
It does not, however, constrain the actual usage of the data store.
Because you will likely update your VBO's and IBO's constantly (see culling) and only want to draw them GL_DYNAMIC_DRAW would be a good choice:
The data store contents will be modified repeatedly (because of culling) and used many times. The data store contents are modified by the application and used as the source for GL drawing and image specification commands.
as I have googled that it can affect the performance of the game
Well, it will cost some performance to cull your terrain but in the end, it will likely gain performance because many vertices (triangles) can be discarded. This performance gain may grow with larger terrains.
I am trying to build a little 'simulation' game.
This game has no real purpose, I am just making small little projects while I try and learn the in's and out's of some beginner programming.
This is my goal:
On the processing canvas, there are multiple 'Nodes' that represent where a player can move to.
The user will input where the player is and where they want to move to. (referencing the nodes)
The program will determine the most efficient route using the A* algorithm.
Once the route has been determined, the player (represented by a Circle()) will move from node to node in a straight line.
Once the player has stopped, the program will know which node the player is currently at AND will wait for further instructions.
I have somehow managed to scrape together the first three of my goals but the second half has been causing me major confusion and headaches.
What I have tried (Goal 4).
I am using a custom library for the A* algorithm which can be found here: http://www.lagers.org.uk/pfind/ref/classes.html.
When the algorithm drew the lines for the optimal route, I would store the X,Y position of each node into an ArrayList.
I would then feed that ArrayList data into my Player Class that would move the circle on the screen via the X/Y positions from the ArrayList.
The issue I had, is that once the player moved to the first node, I had no way of reporting that the player had stopped moving and is ready to move onto the next ArrayList X/Y position.
I managed a workaround by incrementing the ArrayList every 5 seconds using Millis() but I know this is a terrible way of achieving my goal.
This probably does not make a lot of sense but here is a picture of my current output.
I have told the program that I want the Blue Circle to travel from Node 0 to Node 8 on the most efficient route. My current code would move copy the X/Y positions of Node 0,2,8 and save them into an ArrayList.
That ArrayList information would be fed into the player.setTarget() method every 5 seconds to allow time for the circle to move.
Ideally, I would like to scrap the time delay and have the class report when the player has moved to the node successfully AND which node the player is currently on.
import pathfinder.*;
import java.lang.Math;
// PathFinding_01
Graph graph;
// These next 2 are only needed to display
// the nodes and edges.
GraphEdge[] edges;
GraphNode[] nodes;
GraphNode[] route;
// Pathfinder algorithm
IGraphSearch pathFinder;
// Used to indicate the start and end nodes as selected by the user.
GraphNode startNode, endNode;
PImage bg;
Player midBlue;
Player midRed;
int lastHittingBlue = 99;
int lastHittingRed = 99;
int blueSide = 0;
int redSide = 1;
boolean nodeCount = true;
boolean firstRun = true; //Allows data to be collected on the first node.
boolean movement;
int count;
int x;
int y;
float start;
float runtime;
int test = 1;
// create ArrayList for route nodes
ArrayList<Float> xPos;
ArrayList<Float> yPos;
void setup() {
size(1200,1000); //Set size of window to match size of the background image.
bg = loadImage("background.png");
bg.resize(1200,1000);
start = millis();
textSize(20);
// Create graph
createGraph();
// Get nodes and edges
nodes = graph.getNodeArray();
edges = graph.getAllEdgeArray();
// Now get a path finder object
pathFinder = new GraphSearch_Astar(graph);
// Now get a route between 2 nodes
// You can change the parameter values but they must be valid IDs
pathFinder.search(0,8);
route = pathFinder.getRoute();
//Initialise the X/Y position arraylist.
xPos = new ArrayList<Float>();
yPos = new ArrayList<Float>();
drawGraph();
drawPath();
midBlue = new Player(lastHittingBlue, blueSide);
midRed = new Player(lastHittingRed, redSide);
}
void draw() {
background(0);
text((float)millis()/1000, 10,height/6);
text(start/1000, 10,height/3);
runtime = millis() - start;
text(runtime/1000, 10,height/2);
if (runtime >= 5000.0) {
start = millis();
float printX = midBlue.getXPos();
float printY = midBlue.getYPos();
int pX = round(printX);
int pY = round(printY);
print(pX, " ", pY, "\n");
test += 1;
}
drawGraph();
drawPath();
movement = midBlue.movementCheck();
midBlue.setTargetPosition(xPos.get(test), yPos.get(test));
midBlue.drawPlayer();
text( "x: " + mouseX + " y: " + mouseY, mouseX + 2, mouseY );
//noLoop();
}
void drawGraph() {
// Edges first
strokeWeight(2);
stroke(180, 180, 200);
for (int i = 0; i < edges.length; i++) {
GraphNode from = edges[i].from();
GraphNode to = edges[i].to();
line(from.xf(), from.yf(), to.xf(), to.yf());
}
// Nodes next
noStroke();
fill(255, 180, 180);
for (int i = 0; i < nodes.length; i++) {
GraphNode node = nodes[i];
ellipse(node.xf(), node.yf(), 20, 20);
text(node.id(), node.xf() - 24, node.yf() - 10);
}
}
void drawPath() {
strokeWeight(10);
stroke(200, 255, 200, 160);
for (int i = 1; i < route.length; i++) {
GraphNode from = route[i-1];
GraphNode to = route[i];
while (firstRun) {
xPos.add(from.xf());
yPos.add(from.yf());
firstRun = false;
}
xPos.add(to.xf());
yPos.add(to.yf());
line(from.xf(), from.yf(), to.xf(), to.yf());
if (nodeCount == true) {
count = route.length;
nodeCount = false;
}
}
}
public void createGraph() {
graph = new Graph();
// Create and add node
GraphNode node;
// ID X Y
node = new GraphNode(0, 175, 900);
graph.addNode(node);
node = new GraphNode(1, 190, 830);
graph.addNode(node);
node = new GraphNode(2, 240, 890);
graph.addNode(node);
node = new GraphNode(3, 253, 825);
graph.addNode(node);
node = new GraphNode(4, 204, 750);
graph.addNode(node);
node = new GraphNode(5, 315, 770);
graph.addNode(node);
node = new GraphNode(6, 325, 880);
graph.addNode(node);
node = new GraphNode(7, 440, 880);
graph.addNode(node);
node = new GraphNode(8, 442, 770);
graph.addNode(node);
node = new GraphNode(9, 400, 690);
graph.addNode(node);
node = new GraphNode(10, 308, 656);
graph.addNode(node);
node = new GraphNode(11, 210, 636);
graph.addNode(node);
// Edges for node 0
graph.addEdge(0, 1, 0, 0);
graph.addEdge(0, 2, 0, 0);
graph.addEdge(0, 3, 0, 0);
// Edges for node 1
graph.addEdge(1, 4, 0, 0);
graph.addEdge(1, 5, 0, 0);
graph.addEdge(1, 10, 0, 0);
// Edges for node 2
graph.addEdge(2, 5, 0, 0);
graph.addEdge(2, 6, 0, 0);
graph.addEdge(2, 8, 0, 0);
// Edges for node 3
graph.addEdge(3, 5, 0, 0);
graph.addEdge(3, 8, 0, 0);
graph.addEdge(3, 10, 0, 0);
// Edges for node 4
graph.addEdge(4, 10, 0, 0);
graph.addEdge(4, 11, 0, 0);
// Edges for node 5
graph.addEdge(5, 8, 0, 0);
graph.addEdge(5, 9, 0, 0);
graph.addEdge(5, 10, 0, 0);
// Edges for node 6
graph.addEdge(6, 7, 0, 0);
graph.addEdge(6, 8, 0, 0);
// Edges for node 7
graph.addEdge(7, 0, 0, 0);
// Edges for node 7
graph.addEdge(9, 0, 0, 0);
// Edges for node 7
//graph.addEdge(10, 0, 0, 0);
// Edges for node 7
graph.addEdge(11, 0, 0, 0);
}
class Player {
int lastHitting;
int side; //0 = Blue, 1 = Red.
float xPos;
float yPos;
float xTar;
float yTar;
color circleColour = color(255,0,0);
boolean isPlayerStopped;
int xDir;
int yDir;
Player(int lastHitting, int side) {
this.lastHitting = lastHitting;
this.side = side;
/* Set the Colour of the circle depending on their side selection */
if (this.side == 0) {
circleColour = color(0,0,255);
xPos = 180;
yPos = 900;
} else if (this.side == 1) {
circleColour = color(255,0,0);
xPos = 990;
yPos = 125;
}
}
void drawPlayer() {
fill(circleColour);
circle(xPos,yPos,35);
float speed = 100.0;
PVector dir = new PVector(xTar - xPos, yTar - yPos);
while (dir.mag() > 1.0) {
dir.normalize();
dir.mult(min(speed, dir.mag()));
xPos += dir.x;
yPos += dir.y;
isPlayerStopped = false;
}
if (dir.mag() < 1.0) {
isPlayerStopped = true;
}
}
void setTargetPosition(float targetX, float targetY) {
xTar = targetX;
yTar = targetY;
}
boolean movementCheck() {
return isPlayerStopped;
}
float getXPos() {
return xPos;
}
float getYPos() {
return yPos;
}
}
Thank you for your help in advance. I know this is a bit of a loaded question. I am really just looking for direction, I have tried a lot of different things and I'm not sure what tool I am supposed to use to help me progress.
Please don't flame my terrible code too much, I am still very new to all of this.
I am not going to flame your terrible code because I know how steep the learning curve is for what you're doing and I respect that. This said, I think that you would gain much more insight on what's going on if you ditched the library and coded your A* yourself.
If it comes to that, I can help later, but for now, here's what we'll do: I'll point out how you can get this result:
And as a bonus, I'll give you a couple tips to improve on your coding habits.
I can see that you kinda know what you're doing by reading your own understanding of the code (nice post overall btw), but also in the code I can see that you still have a lot to understand about what you're really doing.
You should keep this exactly as it is right now. Email it to yourself to you receive in in one year, this way next year while you despair about getting better you'll have the pleasant surprise to see exactly how much you improved - or, in my case, I just decided that I had been retarded back then and still was, but I hope that you're not that hard on yourself.
As there is A LOT of space for improvement, I'm just going to ignore all of it except a couple key points which are not project specific:
Use explicit variable names. Some of your variables look like they are well named, like nodeCount. Except that this variable isn't an integer; it's a boolean. Then, it should be named something like nodesHaveBeenCounted. Name your variables like if an angry biker had to review your code and he'll break one of your finger every time he has to read into the code to understand what's a variable purpose. While you're at it, try not to shorten a variable name even when it's painfully obvious. xPos should be xPosition. This apply to method signatures, too, both with the method's name (which you're really good at, congrats) and the method's parameters.
Careful with the global variables. I'm not against the idea of using globals, but you should be careful not to just use them to bypass scope. Also, take care not to name local variables and global variables the same, like you did with xPos, which may be an ArrayList or a float depending where you are in the code. Be methodic: you can add something to all your global variables which clearly identify them as globals. Some people name prefix them with a g_, like g_xPos. I like to just use an underscore, like _xPos.
When a method does more than one thing, think about splitting it in smaller parts. It's waaay easier to debug the 8 lines where a value is updated than to sift through 60 lines of code wondering where the magic is happening.
Now here are the changes I made to make the movements smoother and avoid using a timer.
In the global variables:
Rename xPos into xPosArray or something similar, as long as it's not overshadowed by the Player's xPos modal variable. Do the same with the yPos ArrayList.
In the setup() method:
Add this line as the last line of the method (as long as it's after instantiating midBlue and running the drawPath method it'll be right):
midBlue.setTargetPosition(xPosArray.get(test), yPosArray.get(test));
In the draw() method:
Remove the if (runtime >= 5000.0) {...} block entirely, there's no use for it anymore.
Remove these lines:
movement = midBlue.movementCheck();
midBlue.setTargetPosition(xPosArray.get(test), yPosArray.get(test));
In the Player.drawPlayer() method:
Erase everything after and including the while. Replace it with these lines (they are almost the same but the logic is slightly different):
if (dir.mag() > 1.0) {
dir.normalize();
dir.mult(min(speed, dir.mag()));
xPos += dir.x;
yPos += dir.y;
} else {
// We switch target only once, and only when the target has been reached
setTargetPosition(xPosArray.get(test), yPosArray.get(test));
}
Run the program. You don't need a timer anymore. What's the idea? It's simple: we only change the Player's target once the current target has been reached. Never elsewhere.
Bonus idea: instead of a global variable for your array of coordinates, each player should have it's own ArrayList of coordinates. Then each Player will be able to travel independently.
Have fun!
Quick question about Java 2D arrays; For my tile-based, top-down, 2D game (using swing) I use
a 2D array to create a map, like this
public int[][] createMap(){
return new int[][]{
{0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}};
}
I then use this in my gameComponents class where I draw the individual tiles unto the map, like this
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (int row = 0; row < game.getMap().getWidth(); row++) {
for (int col = 0; col < game.getMap().getHeight(); col++) {
g.drawImage(tile.getTileImage().get(values()[game.getMap().getMapArray()[col][row]]), row * SIZE,
col * SIZE, this);
}
} }
(where size is the size of a tile)
This works, and it correctly draws each tile to the map as expected, however
this also causes a problem for collision detection. As you may have noted, while I do define the size between the tiles in draw method, it is not defined in the array at all. Which, as you'd imagine, raises issues when checking for collision as the drawn tile is not where the tile is in the 2D array (due to size offset).
This is the code I use for checking collision (of course, not working due to ArrayIndexOutofbounds).
public boolean collisionDetected(int xDirection, int yDirection, Game game, Player player){
for (int row = 0; row < game.getMap().getHeight() * 16; row ++){
for (int col = 0; col < game.getMap().getWidth() * 16; col++) {
System.out.println(col + xDirection + player.getPositionX());
if(game.getMap().getTile(col + xDirection + player.getPositionX() ,
row + yDirection + player.getPositionY()) == Tiles.GRASS ){
System.out.println("COLLISION DETECTED");
return true;
}
}
}
return false;
}
This method uses a method within the map class that returns the tile on that
specific coordinate, like this
public Tiles getTile(int col,int row){
return Tiles.values()[mapArray[col][row]];
}
And, of course, as the 2D array doesn't know of the size offset, it just throws
an arrayindexoutofbound.
My question is, is it possible to define a 2D map array with the size of a tile in-mind? I appreciate any help & input I can get, after-all I am here to learn!
Extra clarification: All the tiles are in an enum class (i.e AIR, GRASS, STONE...). Also worth noting that the player position is not bound by an array, I merely move it the amount of pixels I want it to move.
Thanks in advance!
This method uses a method within the map class that returns the tile on that specific coordinate, like this
public Tiles getTile(int col,int row){
return Tiles.values()[mapArray[col][row]];
}
So if you have a "coordinate", why do you call the parameters col/row?
If you have a 10x10 grid and each tile is 20 pixels then the grid size is 200x200 so you could have x/y values in the range 0-199
So if you have a coordinate of 25x35 you would simply calculate the row/col values as:
int row = 35 / 20;
int column = 25 / 20;
So your method would be something like :
public Tiles getTile(int x, int y)
{
int row = y / 20;
int column = x / 20;
return Tiles.values()[mapArray[row][column]];
}
So I am making a simple game using LibGDX which involes a 150*150 hexagonal map, and various types of cell, rocky, clear etc.
Problem is when i load the map, my computer almost completely freezes up and any movement thats supposed to be fluid (character moving, button highlights) take 5+ seconds longer than they should.
Here's the relevant code:
public void render(float deltY){
Gdx.gl.glClearColor(255, 255, 255, 100);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
polygonSpriteBatch.begin();
for (int j = 0; j < 150; j++) {
for (int i = 0; i < 150; i++) {
offset = i%2 == 0 ? multipleX/2 : 0;
if (mc.getMap().getRow(i).getTile(j).getTileType().equals(TileType.Rocky)) {
drawCell(Color.BLACK, j, i);}
if (mc.getMap().getRow(i).getTile(j).getTileType().equals(TileType.Clear)) {
drawCell(Color.LIGHT_GRAY, j, i);}
}
}
polygonSpriteBatch.end();
stage.draw();
}
private void drawCell(Color color, int x, int y) {
polySprite = new PolygonSprite(makePoints(color));
polySprite.setX(mc.getMap().getRow(y).getTile(x).getTilePosition().get_x() * multipleX + offset);
polySprite.setY(mc.getMap().getRow(y).getTile(x).getTilePosition().get_y() * multipleY);
polySprite.draw(polygonSpriteBatch);
}
public PolygonRegion makePoints(Color color){
side = 5;
h = CalculateH(side);
r = CalculateR(side);
multipleX = (float)Math.sqrt(3)*side;
multipleY = side+(side/2);
float[] points = { // vertices
x, y,
x+r, y+h,
x+r, y+side+h,
x,y+side+h+h,
x-r, y+side+h,
x-r, y+h};
return new PolygonRegion(new TextureRegion(getTexture(color)),points
, new short[] { //4 triangles using vertices to make hexagon
0, 1, 5,
1, 4, 2,
5, 1, 4,
2, 3, 4});
}
public Texture getTexture(Color color){
Pixmap pix = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pix.setColor(color);
pix.fill();
textureSolid = new Texture(pix);
return textureSolid;
}
I'm new to coding and LibGDX so there's probably something stupid i'm doing. Is there any way to render the map once and only redraw the polygons if they change?
Thanks
Looking at your code, you are computing a square root for each cell, for each rendering pass.
So your code currently involves more than 22500 square root operations for each frame you render and is creating as many objects, that's quite a lot !
You should compute the points for your hexagons only once.
i am making a opengl engine for learning purposes and in a later stadium develop some games/apps with it. Now with the newest iteration of my engine i cant seem to find out why my quads aren't being colored.
Each vertex is composed of 4 coords (x, y, z, w)and 4 colorcoords r, g, b, a. As far as i can tell the values i pass into the vertices are correct
The offset calculations are hidden away in a seperate static class. i have added it at the bottom of the post.
Vertex[] vertices = new Vertex[] { v0, v1, v2, v3 };
verticesBuffer = BufferUtils.createFloatBuffer(vertices.length * ELEMENT);
for (int i = 0; i < vertices.length; i++) {
verticesBuffer.put(vertices[i].getElements());
}
verticesBuffer.flip();
indicesBuffer = BufferUtils.createByteBuffer(indices.length);
indicesBuffer.put(indices);
indicesBuffer.flip();
int vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);
int vboId = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(0, POSITION_ELEMENT, GL_FLOAT, false, ELEMENT_BYTES, POSITION_OFFSET);
glVertexAttribPointer(1, COLOR_ELEMENTS, GL_FLOAT, false, ELEMENT_BYTES, COLOR_OFFSET);
glBindBuffer(GL_ARRAY_BUFFER, 0);
int vboIId = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
Shaders shader = new Shaders();
int vsId = shader.load("shaders/shader.vert", GL_VERTEX_SHADER);
int fsId = shader.load("shaders/shader.frag", GL_FRAGMENT_SHADER);
pId = glCreateProgram();
glAttachShader(pId, vsId);
glAttachShader(pId, fsId);
glBindAttribLocation(pId, 0, "in_Position");
glBindAttribLocation(pId, 1, "in_Color");
glBindAttribLocation(pId, 2, "in_TextureCoord");
glLinkProgram(pId);
glValidateProgram(pId);
My render call is in another class that i call after i passed through the vaoId, vboIID, the amount of indices and the pId
glClear(GL_COLOR_BUFFER_BIT); // scherm schoonmaken
glUseProgram(pId);
glBindVertexArray(vaoId);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIId);
glDrawElements(GL_TRIANGLES, amount_of_indices, GL_UNSIGNED_BYTE, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindVertexArray(0);
Seperate static class with offset calculations
// Amount of bytes per element
static final int BYTES_PER_ELEMENT = 4;
// Elements per parameter
static final int POSITION_ELEMENT = 4;
static final int COLOR_ELEMENTS = 4;
static final int TEXTURE_ELEMENTS = 2;
// Bytes per parameter
static final int POSITION_BYTES = POSITION_ELEMENT * BYTES_PER_ELEMENT;
static final int COLOR_BYTES = COLOR_ELEMENTS * BYTES_PER_ELEMENT;
static final int TEXTURE_BYTES = TEXTURE_ELEMENTS * BYTES_PER_ELEMENT;
// Byte offset per parameter
static final int POSITION_OFFSET = 0;
static final int COLOR_OFFSET = POSITION_OFFSET + POSITION_BYTES;
static final int TEXTURE_OFFSET = COLOR_OFFSET + COLOR_BYTES;
// Amount of elements per vertex
static final int ELEMENT = POSITION_ELEMENT + COLOR_ELEMENTS + TEXTURE_ELEMENTS;
// Byte size per vertex
static final int ELEMENT_BYTES = POSITION_BYTES + COLOR_BYTES + TEXTURE_BYTES;
As far as i can tell my offset calculations are correct. I had positions reserved for texture mapping but i removed them to see if those aren't causing any problems.
A full version of the code is at github https://github.com/darR3Ke/EngineWorks-2.0
nm, i've solved it.
Somehow copying the example shaders from the lwjgl for the texture quads is interfering with my non textured quad.
So for all those following the lwjgl tutorials on the wiki. the Texture quad shaders are not compatible with the colored quad shaders.
Still need to wait a couple of days before i can check this awnser