I'm trying to load an explosion animation. The animations consists of 16 frames, all saved in the file Explosion.png. In my game, all the images are stored in a texture atlas pack.
So first i got the region that i needed from the class Assets.java
public class Explosion {
public final AtlasRegion explosion;
public Explosion (TextureAtlas atlas){
explosion = atlas.findRegion(Constants.EXPLOSION);
}
}
Then in my class which will create the explosion, I have the following code:
public Particles(Vector2 position){
this.position = position;
startTime = TimeUtils.nanoTime();
Array<TextureRegion> explosionAnimationTexture = new Array<TextureRegion>();
TextureRegion region = Assets.instance.explosion.explosion;
Texture explosionTexture = region.getTexture();
int ROW = 4; // rows of sprite sheet image
int COLUMN = 4;
TextureRegion[][] tmp = TextureRegion.split(explosionTexture, explosionTexture.getWidth() / COLUMN, explosionTexture.getHeight() / ROW);
TextureRegion[] frames = new TextureRegion[ROW * COLUMN];
int elementIndex = 0;
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COLUMN; j++) {
explosionAnimationTexture.add(tmp[i][j]);
frames[elementIndex++] = tmp[i][j];
}
}
explosion = new Animation(EXPLOSION_FRAME_DURATION,explosionAnimationTexture , Animation.PlayMode.LOOP_PINGPONG);
}
I'm using 4x4 since I have 16 frame. And inside the render method i got the following:
public void render(SpriteBatch batch){
float elapsedTime = MathUtils.nanoToSec * (TimeUtils.nanoTime() - startTime);
TextureRegion walkLoopTexture = explosion.getKeyFrame(elapsedTime);
batch.draw(
walkLoopTexture.getTexture(),
position.x,
position.y,
0,
0,
walkLoopTexture.getRegionWidth(),
walkLoopTexture.getRegionHeight(),
0.3f,
0.3f,
0,
walkLoopTexture.getRegionX(),
walkLoopTexture.getRegionY(),
walkLoopTexture.getRegionWidth(),
walkLoopTexture.getRegionHeight(),
false,
false);
}
The animation is working, however the images are loading from the whole atlas file, and not only Explosion.png as specified in step 1.
Code inside your Particles class :
TextureRegion region = Assets.instance.explosion.explosion;
TextureRegion[][] tmp = TextureRegion.split(explosionTexture, explosionTexture.getWidth() / COLUMN, explosionTexture.getHeight() / ROW);
replace with :
TextureRegion[][] tmp = region.split(region.getRegionWidth()/COLUMN,region.getRegionHeight()/ROW);
And
draw your Animation using textureRegion instead of his texture so choose appropriate method signature of SpriteBatch for drawing textureRegion.
Related
I'm new to LibGDX and I'm trying to make a simple RPG game.
I've implemented a basic movement & combat system.
Now I would like to add a sidebar with inventory, character information etc. (like in Tibia).
Then I would like to add a bottom bar too.
However, I don't know how to accomplish that. I've read that adding 2nd stage could be a solution, but I don't know how implement it into my code.
screen how that supposed to look like
Here is my current code with render method:
public class Game extends ApplicationAdapter {
private final static int TURN_DURATION_IN_MILLIS = 2000;
TiledMap tiledMap;
OrthographicCamera gameCamera;
OrthographicCamera guiCamera;
private Map renderer;
Player player;
Monster rat;
private Map.Drawing playerDrawing;
private Map.Drawing ratDrawing;
private SpriteBatch sb;
BitmapFont font;
BitmapFont font2;
Stage stage;
float guiToCameraRatioX;
float guiToCameraRatioY;
long lastTurn;
#Override
public void create() {
sb = new SpriteBatch();
tiledMap = new TmxMapLoader().load("map.tmx");
renderer = new Map(tiledMap, 1 / TILE_CELL_IN_PX, sb);
gameCamera = new OrthographicCamera(NUMBER_OF_TILES_HORIZONTALLY, NUMBER_OF_TILES_VERTICALLY);
gameCamera.update();
guiCamera = new OrthographicCamera(GAME_WINDOW_WIDTH, GAME_WINDOW_HEIGHT);
guiCamera.update();
guiToCameraRatioX = guiCamera.viewportWidth / gameCamera.viewportWidth;
guiToCameraRatioY = guiCamera.viewportHeight / gameCamera.viewportHeight;
player = new Player();
playerDrawing = new Map.Drawing(true, null, player.positionX - 0.5f, player.positionY + 0.5f, player.width, player.height, gameCamera, guiCamera);
player.initHealthPointsBar();
renderer.addCreature(player);
stage = new TiledMapStage(tiledMap, player);
Gdx.input.setInputProcessor(stage);
stage.getViewport().setCamera(gameCamera);
rat = new Monster(MonsterType.RAT);
ratDrawing = new Map.Drawing(false, null, rat.positionX, rat.positionY, rat.width, rat.height, gameCamera, guiCamera);
renderer.addDrawing(ratDrawing);
renderer.addDrawing(playerDrawing);
rat.initHealthPointsBar();
renderer.addCreature(rat);
initFont();
}
private void initFont() {
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("martel.ttf"));
FreeTypeFontGenerator.FreeTypeFontParameter parameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
parameter.size = 10;
parameter.color = new Color(0, 0.75f, 0.15f, 1);
parameter.borderWidth = 1.2f;
font = generator.generateFont(parameter);
parameter.size = 12;
parameter.color = new Color(0.8f, 0.1f, 0.1f, 1);
font2 = generator.generateFont(parameter);
generator.dispose();
}
#Override
public void render() {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
float deltaTime = Gdx.graphics.getDeltaTime();
player.updateState(deltaTime);
rat.updateState(deltaTime);
gameCamera.position.set(player.positionX + 0.5f, player.positionY + 0.5f, 0);
gameCamera.update();
renderer.setView(gameCamera);
renderer.render();
stage.act();
playerDrawing.x = player.positionX;
playerDrawing.y = player.positionY;
ratDrawing.x = rat.positionX;
ratDrawing.y = rat.positionY;
//TODO implement real combat system
if (System.currentTimeMillis() - lastTurn >= TURN_DURATION_IN_MILLIS) {
lastTurn = System.currentTimeMillis();
if (rat.state != Creature.State.DEAD && Math.abs(rat.positionX - player.positionX) < 2 && Math.abs(rat.positionY - player.positionY) < 2) {
player.givenHit = (int) (Math.random() * rat.attack + 1) * 2 - player.defence / 2;
player.currentHealthPoints -= player.givenHit;
rat.givenHit = (int) (Math.random() * player.attack + 1) * 2 - rat.defence / 2;
rat.currentHealthPoints -= rat.givenHit;
if (rat.currentHealthPoints <= 0) {
rat.currentHealthPoints = 0;
rat.state = Creature.State.DEAD;
rat.moveDestinationX = -1;
rat.moveDestinationY = -1;
}
} else {
player.givenHit = 0;
rat.givenHit = 0;
}
}
// used solution below https://stackoverflow.com/questions/20595558/libgdx-sprite-batch-font-bad-scale-rendering
player.renderPlayer(font, font2, playerDrawing, sb, guiCamera.position.x - (gameCamera.position.x - player.positionX) * guiToCameraRatioX, guiCamera.position.y - (gameCamera.position.y - player.positionY) * guiToCameraRatioY, player.givenHit, System.currentTimeMillis() - lastTurn);
rat.renderMonster(font, font2, ratDrawing, sb, guiCamera.position.x - (gameCamera.position.x - rat.positionX) * guiToCameraRatioX, guiCamera.position.y - (gameCamera.position.y - rat.positionY) * guiToCameraRatioY, rat.givenHit, System.currentTimeMillis() - lastTurn);
}
#Override
public void dispose() { // SpriteBatches and Textures must always be disposed
sb.dispose();
}
I appreciate any help :)
You already have a guiCamera in your Game class. This one can be used to draw the sidebar (or anything else that doesn't move with the rest of the map).
If this camera is already used (e.g. for a menu), you could create a new one, so it can be changed if needed (without changing the menu, or whatever it is used for).
Basically you just need to set the camera's projection matrix to you SpriteBatch (maybe better use a new SpriteBatch for this) and start drawing.
A solution could look like this:
private SpriteBatch sb2; // better use a different sprite batch here (and initialize it in your create method, just like the other one)
// ...
#Override
public void render() {
// ... other rendering stuff ...
// set the projection matrix
sb2.setProjectionMatrix(guiCamera.combined);
sb2.begin();
// draw whatever you want on your sidebar to appear
// all things you draw here will be static on the screen, because the guiCamera doesn't move with the player, but stays in it's position
// when you are ready drawing, end the SpriteBatch, so everything is actually drawn
}
sb2.end();
I am using a tutorial to understand how sprites work using a draw() method as well as using a gameloop. I adjusted the code as far as I understand it for my own project.
The question I have is how can I access a different row of my sprite sheet besides the second row. My sprite sheet has 9 columns and 20 rows.
public class Sprite implements Drawable {
private static final int BMP_COLUMNS = 9;
private static final int BMP_ROWS = 20;
private int x = 0;
private int y = 0;
private int xSpeed = 5;
private Bitmap bmp;
float fracsect = 30;
private GameContent gameContent;
private int currentFrame = 0;
private int width;
private int height;
public Sprite(GameContent gameContent, Bitmap bmp) {
this.gameContent = gameContent;
this.bmp = bmp;
this.width = bmp.getWidth() / BMP_COLUMNS;
this.height = bmp.getHeight() / BMP_ROWS;
}
#Override
public void update(float fracsec) {
if (x > gameContent.getGameWidth() - width - xSpeed) {
xSpeed = -5;
}
if (x + xSpeed < 0) {
xSpeed = 5;
}
x = x + xSpeed;
currentFrame = ++currentFrame % BMP_COLUMNS;
}
#Override
public void draw(Canvas canvas) {
update(fracsect);
int srcX = currentFrame * width;
int srcY = 1*height - 41;
Rect src = new Rect(srcX +20 , srcY,srcX + width,srcY + height-38); // Generates
Rect dst = new Rect(x,y,x+width -30, y+height-30); // Scales
canvas.drawBitmap(bmp, src, dst, null);
}
}
How do I gain access to the second row and how can I change it for instance to the third or 4th row ?
What I understand so far is that using a sprite as an object as a bitmap instead via imageview the code implementation of the code works differently. Is there any advice on how to access a different row for the sprite sheet ? I used the android documentation as well as the tutorial to understand this process as far as I can.
Here is the tutorial also:
http://www.edu4java.com/en/androidgame/androidgame4.html
From what I can see you are iterating through the first row, drawing each sprite in turn, perhaps to create an animation.
If you want to draw the animation for the second row, you can simply add 'height' to 'srcY'. It looks like you had this idea but you substracted instead of adding. Remember that Y-coordinates on computers go from top to bottom (i.e. (0,0) is top-left, (0, 10) is 10 pixels lower).
Likewise for the third and fourth rows, just add 'height' to 'srcY' again.
I'm teaching myself LibGdx and was following the simple game tutorial, unfortunately majority of the code is in one class. I want to refactor the code so I can use multiple textures for the rain that falls based on a random number.
I'll attach the Code for the main program and then the class I got started on.
So far everything worked except the Rain texture/img does not show on the screen.
public class GameScreen implements Screen {
public static FruitHarvest game;
protected final Texture dropImage;
//protected final Texture dropImage2;
private final Texture bucketImage;
public static Rectangle bucket;
public static Sound dropSound;
//private static Music rainMusic;
private final OrthographicCamera camera;
public static Array<Rectangle> raindrops;
private long lastDropTime;
public static int dropsGathered;
// private int random = MathUtils.random(0,1);
private Drops drop;
//Iterator<Rectangle> iterator = raindrops.iterator();
public GameScreen(final FruitHarvest game) {
this.game = game;
// load the images for the droplet and the bucket, 64x64 pixels each
dropImage = new Texture(Gdx.files.internal("droplet.png"));
//dropImage2 = new Texture(Gdx.files.internal("droplet1.png"));
bucketImage = new Texture(Gdx.files.internal("bucket.png"));
// load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
//rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
//rainMusic.setLooping(true);
// create the camera and the SpriteBatcher
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
// create a Rectangle to logically represent the bucket
bucket = new Rectangle();
bucket.x = 800 / 2 - 64 / 2; // Center the bucket horizontally
bucket.y = 20; // Bottom left corner of the bucket is 20 pixels above the bottom screen edge;
bucket.width = 64;
bucket.height = 64;
// Create the raindrops array and spawn the first raindrop
raindrops = new Array<Rectangle>();
long delta = 0;
drop = new Drops(dropImage, 64, 64, raindrops, delta);
}
#Override
public void render(float delta) {
// clear the screen with a dark blue color. The arguments to glClearColor are the
// red, green, blue, and alpha component in the range [0,1] of the color to be
// used to clear the screen
Gdx.gl.glClearColor(0, 0, .5f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// tell the camera to update its matrices.
camera.update();
// tell the SpriteBatch to render in the coordinate system specified by the camera.
game.batch.setProjectionMatrix(camera.combined);
// begin a new batch and draw the bucket and all drops
game.batch.begin();
game.font.draw(game.batch, "Drops collected: " + dropsGathered, 0, 480);
game.batch.draw(bucketImage, bucket.x, bucket.y, bucket.width, bucket.height);
// Draws the Items Falling
for (Rectangle raindrop : raindrops) {
game.batch.draw(dropImage, raindrop.x, raindrop.y);
}
game.batch.end();
// process user input
if (Gdx.input.isTouched()) {
Vector3 touchPos = new Vector3();
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
bucket.x = touchPos.x - 64 / 2;
}
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
// make sure the bucket stays within the screen bounds
if (bucket.x < 0) bucket.x = 0;
if (bucket.x > 800 - 64) bucket.x = 800 - 64;
// check if we need to create a new raindrop
if (TimeUtils.nanoTime() - drop.getLastDropTime() > 1000000000) {
drop.spawnRaindrop();
}
// move the raindrops, remove any that are beneath the bottom edge of the screen
// or that hit the bucket. In the later case we increase the value our drops counter
// and add a sound effect.
Iterator<Rectangle> iter = raindrops.iterator();
drop.update(delta);
// while (iter.hasNext()) {
// Rectangle raindrop = iter.next();
// raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
// if (raindrop.y + 64 < 0) iter.remove();
// if (raindrop.overlaps(bucket)) {
// dropsGathered++;
// dropSound.play();
// iter.remove();
// }
// }
}
private void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
// public void randomDrop(int value, float dropX, float dropY) {
// switch (value) {
// case 0:
// game.batch.draw(dropImage, dropX, dropY);
// break;
// case 1:
// //game.batch.draw(dropImage2, dropX, dropY);
// break;
// default:
// game.batch.draw(dropImage, dropX, dropY);
// break;
// }
// }
#Override
public void resize(int width, int height) {
}
#Override
public void show() {
// start the playback of the background music when the screen is shown
//rainMusic.play();
}
#Override
public void hide() {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
//rainMusic.dispose();
}
}
Heres my class for the drops
public class Drops {
private Rectangle raindrop;
private int imageHeight, imageWidth, x, y;
private Array<Rectangle> raindrops;
private long lastDropTime;
private Texture dropImage = new Texture(Gdx.files.internal("droplet.png"));
Iterator<Rectangle> iter = GameScreen.raindrops.iterator();
private float runTime = 0;
public Drops(Texture img, int imageHeight, int imageWidth, Array<Rectangle> drop, float delta) {
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
this.raindrops = drop;
this.dropImage = img;
}
public void update(float delta) {
while (iter.equals(true)) {
raindrop = iter.next();
raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
if (raindrop.y + 64 < 0) iter.remove();
onCollision();
}
}
public void onCollision() {
if (raindrop.overlaps(bucket)) {
GameScreen.dropsGathered++;
GameScreen.dropSound.play();
iter.remove();
}
}
public void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = imageWidth;
raindrop.height = imageHeight;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
public long getLastDropTime() {
return lastDropTime;
}
}
By drop.spawnRaindrop(); you add drops to Array<Rectangle> raindrops; in your Drops class but for drawing you use
for (Rectangle raindrop : raindrops) {
game.batch.draw(dropImage, raindrop.x, raindrop.y);
}
Which will loop trough raindrop array list in your GameScreen which is empty.
So either draw the array list in drops or populate array list in GameScreen.
You need to be more careful as you refactor. You left behind your original Array of drop rectangles in your screen class, and you're drawing that (which is now empty). Then in your Drops class you are referencing the iterator for the now useless array in the screen class. And you're updating that empty array in the screen's render method.
Basically, the drops need to be handled in one place, but you're handling redundant arrays of drops in two different classes and getting them all mixed up.
It's not clear to me why you even have a class called Drops that tries to handle collisions with a bucket. There's no reason to move top-level game logic into a separate class, as that just complicates the code. If you had a more complicated game, it might make sense to have separate classes for tracking and updating various aspects of the game.
Incidentally, you're leaking a texture you load in this line:
private Texture dropImage = new Texture(Gdx.files.internal("droplet.png"));
because you never dispose of it before replacing the reference with another one in the constructor. In LibGDX, any object that implements Disposable must be disposed before its reference is lost, or it will leak native memory.
The straight-forward way to allow multiple drop images:
1) Go back to your original single class with all the game logic in the screen class.
2) Load your drop images into an array for easier access.
private final Array<Texture> dropImages = new Array<Texture>(); // replaces your dropImage declaration
//...
// in constructor:
dropImages.add(new Texture(Gdx.files.internal("droplet.png")));
dropImages.add(new Texture(Gdx.files.internal("droplet1.png")));
// etc. as many variations as you like
// don't forget to dispose of them:
#Override
public void dispose() {
for (Texture dropImage : dropImages) dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
}
3) Create a class Drop that extends Rectangle and has an additional parameter for the image type. You probably also want to make these sortable by image index to avoid swapping between Textures multiple times as you draw them, which causes batch flushes since you're not using a TextureAtlas.
public class Drop extends Rectangle implements Comparable<Drop>{
public int imageIndex;
public Drop (){
super();
}
public int compareTo(Drop otherDrop) {
return (int)Math.signum(imageIndex - otherDrop.imageIndex);
}
}
4) Change your Array<Rectangle> to Array<Drop>. When you spawn a drop, also give it a random image index:
private void spawnRaindrop() {
Drop raindrop = new Drop ();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrop.imageIndex = MathUtils.random(dropImages.size); // <-- HERE
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
5) When drawing your drops, use the drop's imageIndex to pull the correct texture. You can sort them first to avoid swapping the Texture back and forth:
// Draws the Items Falling
raindrops.sort();
for (Drop raindrop : raindrops) {
game.batch.draw(dropImages.get(raindrop.imageIndex), raindrop.x, raindrop.y);
}
I'm trying to draw a curve line using BSpline and ShapeRenderer.
With perspective camera I found myself able to do that.
If I try to use OrthographicCamera, instead, nothing gets rendered on the screen.
My goal is to draw a BSPline with ShapeRenderer and a Sprite that follow the same design as the Shape.
Can anyone tell me how to fix this / what I am doing wrong?
thanks a lot
public class MyGdxGame extends GdxTest implements ApplicationListener {
private SpriteBatch spriteBatch;
ParticleEffect effect;
int emitterIndex;
Array<ParticleEmitter> emitters;
int particleCount = 10;
float fpsCounter;
InputProcessor inputProcessor;
int CAMERA_WIDTH = 640; //Gdx.graphics.getWidth(); if use this function an exception is arose
int CAMERA_HEIGHT = 480; //Gdx.graphics.getHeight(); if use this function an exception is arose
BspHbCached hb;
ShapeRenderer renderer;
int nPoints;
private OrthographicCamera camera;
#Override
public void create () {
hb = new BspHbCached(CAMERA_WIDTH/4, CAMERA_WIDTH/4); // caching points for curve to draw
renderer = new ShapeRenderer();
nPoints = hb.getNumSamplePoints();
camera = new OrthographicCamera(CAMERA_WIDTH, CAMERA_HEIGHT);
camera.position.set(CAMERA_WIDTH / 2f, CAMERA_HEIGHT / 2f, 0);
camera.update();
spriteBatch = new SpriteBatch();
effect = new ParticleEffect();
effect.load(Gdx.files.internal("data/test.p"), Gdx.files.internal("data"));
effect.setPosition(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2);
// Of course, a ParticleEffect is normally just used, without messing around with its emitters.
emitters = new Array(effect.getEmitters());
effect.getEmitters().clear();
effect.getEmitters().add(emitters.get(0));
inputProcessor = new InputProcessor() {
public boolean touchUp (int x, int y, int pointer, int button) {
return false;
}
Here are my other important methods:
public void render () {
float delta = Gdx.graphics.getDeltaTime();
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
renderer.setProjectionMatrix(camera.combined);
spriteBatch.setProjectionMatrix(camera.combined);
for(int i = 0; i < nPoints - 1; i++) // all points contained in bspline to draw
{
renderer.begin(ShapeType.Line);
renderer.setColor(Color.RED);
renderer.line(p1,p2);
renderer.end();
}
spriteBatch.begin();
effect.draw(spriteBatch, delta);
spriteBatch.end();
fpsCounter += delta;
if (fpsCounter > 3) {
fpsCounter = 0;
int activeCount = emitters.get(emitterIndex).getActiveCount();
Gdx.app.log("libgdx", activeCount + "/" + particleCount + " particles, FPS: " + Gdx.graphics.getFramesPerSecond());
}
}
}
NB: my starting point was Path Interface Splines
Backstory:
I'm trying to draw as many squares the the screen as possible using a single draw call. I'm using a custom glsl vertex shader that is specialized for 2D drawing, and that is supposed to be pulling position data for the vertices of the squares from a samplerBuffer. Since I don't need to worry about rotating or scaling the squares all I should need to do is load the position data into a buffer, bind a texture to that buffer, and then use the sampler to get each vertex's position in the shader. In order to get an index into the texture I store each elements index as the z-component of the vertices.
Everything seems to work really well for a thousand or so squares, but after that I start to get weird blinking. It sort of seems like it's not drawing all of the squares every draw step, or possibly not using all of the positions so that many of the squares are overlapping.
The weird thing is, that if I use drawElements instead of drawElementsMulti, the blinking goes away (but of course then all the squares are drawn as one single object, which I don't want)
One question I have is if my position data is limited to the max texture size, or the max texture buffer size. And if I am limited to the much smaller max texture size, how do I get around it? There's got to be a reason all of that texture buffer space is there, but I obviously don't get how to properly use it.
I'm also thinking maybe glMultiDrawElements is doing something I'm not accounting for with the sampler somehow. Idk, I'm really lost at this point, and yet..it works perfectly for smaller numbers of squares, so I must be doing something right.
[EDIT] Code had changed to reflect suggestions below (and for readability), but the problem persists.
Ok, so here's some code. First the vertex shader:
uniform mat3 projection;
attribute vec3 vertex;
uniform samplerBuffer positionSampler;
attribute vec4 vertex_color;
varying vec4 color;
float positionFetch(int index)
{
// I've tried texelFetch here as well, same effect
float value = texelFetchBuffer(positionSampler, index).r;
return value;
}
void main(void)
{
color = vec4(1, 1, 1, 1);
// use the z-component of the vertex to look up the position of this instance in the texture
vec3 real_position = vec3(vertex.x + positionFetch(int(vertex.z)*2), vertex.y + positionFetch(int(vertex.z)*2+1), 1);
gl_Position = vec4(projection * real_position, 1);
}
And now my GLRenderer, sorry there is so much code, I just really want to make sure there's enough info here to get an answer. This has really been driving me nuts, and examples for java seem to be hard to come by (maybe this code will help someone else on their quest):
public class GLRenderer extends GLCanvas implements GLEventListener, WindowListener
{
private static final long serialVersionUID = -8513201172428486833L;
private static final int bytesPerFloat = Float.SIZE / Byte.SIZE;
private static final int bytesPerShort = Short.SIZE / Byte.SIZE;
public float viewWidth, viewHeight;
public float screenWidth, screenHeight;
private FPSAnimator animator;
private boolean didInit = false;
JFrame the_frame;
SquareGeometry geometry;
// Thought power of 2 might be required, doesn't seem to make a difference
private static final int NUM_THINGS = 2*2*2*2*2*2*2*2*2*2*2*2*2*2;
float[] position = new float[NUM_THINGS*2];
// Shader attributes
private int shaderProgram, projectionAttribute, vertexAttribute, positionAttribute;
public static void main(String[] args)
{
new GLRenderer();
}
public GLRenderer()
{
// setup OpenGL Version 2
super(new GLCapabilities(GLProfile.get(GLProfile.GL2)));
addGLEventListener(this);
setSize(1800, 1000);
the_frame = new JFrame("Hello World");
the_frame.getContentPane().add(this);
the_frame.setSize(the_frame.getContentPane().getPreferredSize());
the_frame.setVisible(true);
the_frame.addWindowListener(this);
animator = new FPSAnimator(this, 60);
animator.start();
}
// Called by the drivers when the gl context is first made available
public void init(GLAutoDrawable d)
{
final GL2 gl = d.getGL().getGL2();
IntBuffer asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_BUFFER_SIZE, asd);
System.out.println(asd.get(0));
asd = IntBuffer.allocate(1);
gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_SIZE, asd);
System.out.println(asd.get(0));
shaderProgram = ShaderLoader.compileProgram(gl, "default");
gl.glLinkProgram(shaderProgram);
_getShaderAttributes(gl);
gl.glUseProgram(shaderProgram);
_checkGLCapabilities(gl);
_initGLSettings(gl);
// Calculate batch of vertex data from dirt geometry
geometry = new SquareGeometry(.1f);
geometry.buildGeometry(viewWidth, viewHeight);
geometry.finalizeGeometry(NUM_THINGS);
geometry.vertexBufferID = _generateBufferID(gl);
_loadVertexBuffer(gl, geometry);
geometry.indexBufferID = _generateBufferID(gl);
_loadIndexBuffer(gl, geometry);
geometry.positionBufferID = _generateBufferID(gl);
// initialize buffer object
int size = NUM_THINGS * 2 * bytesPerFloat;
System.out.println(size);
IntBuffer bla = IntBuffer.allocate(1);
gl.glGenTextures(1, bla);
geometry.positionTextureID = bla.get(0);
gl.glUniform1i(positionAttribute, 0);
gl.glActiveTexture(GL2.GL_TEXTURE0);
gl.glBindTexture(GL2.GL_TEXTURE_BUFFER, geometry.positionTextureID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
gl.glBufferData(GL2.GL_TEXTURE_BUFFER, size, null, GL2.GL_DYNAMIC_DRAW);
gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
}
private void _initGLSettings(GL2 gl)
{
gl.glClearColor(0f, 0f, 0f, 1f);
}
private void _loadIndexBuffer(GL2 gl, SquareGeometry geometry)
{
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, bytesPerShort*NUM_THINGS*geometry.getNumPoints(), geometry.indexBuffer, GL2.GL_STATIC_DRAW);
}
private void _loadVertexBuffer(GL2 gl, SquareGeometry geometry)
{
int numBytes = geometry.getNumPoints() * 3 * bytesPerFloat * NUM_THINGS;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
gl.glBufferData(GL2.GL_ARRAY_BUFFER, numBytes, geometry.vertexBuffer, GL2.GL_STATIC_DRAW);
gl.glEnableVertexAttribArray(vertexAttribute);
gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
}
private int _generateBufferID(GL2 gl)
{
IntBuffer bufferIDBuffer = IntBuffer.allocate(1);
gl.glGenBuffers(1, bufferIDBuffer);
return bufferIDBuffer.get(0);
}
private void _checkGLCapabilities(GL2 gl)
{
// TODO: Respond to this information in a meaningful way.
boolean VBOsupported = gl.isFunctionAvailable("glGenBuffersARB") && gl.isFunctionAvailable("glBindBufferARB")
&& gl.isFunctionAvailable("glBufferDataARB") && gl.isFunctionAvailable("glDeleteBuffersARB");
System.out.println("VBO Supported: " + VBOsupported);
}
private void _getShaderAttributes(GL2 gl)
{
vertexAttribute = gl.glGetAttribLocation(shaderProgram, "vertex");
projectionAttribute = gl.glGetUniformLocation(shaderProgram, "projection");
positionAttribute = gl.glGetUniformLocation(shaderProgram, "positionSampler");
}
// Called by me on the first resize call, useful for things that can't be initialized until the screen size is known
public void viewInit(GL2 gl)
{
for(int i = 0; i < NUM_THINGS; i++)
{
position[i*2] = (float) (Math.random()*viewWidth);
position[i*2+1] = (float) (Math.random()*viewHeight);
}
gl.glUniformMatrix3fv(projectionAttribute, 1, false, Matrix.projection3f, 0);
// Load position data into a texture buffer
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
ByteBuffer textureBuffer = gl.glMapBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_WRITE_ONLY);
FloatBuffer textureFloatBuffer = textureBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
for(int i = 0; i < position.length; i++)
{
textureFloatBuffer.put(position[i]);
}
gl.glUnmapBuffer(GL2.GL_TEXTURE_BUFFER);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, 0);
}
public void display(GLAutoDrawable d)
{
if (!didInit || geometry.vertexBufferID == 0)
{
return;
}
//long startDrawTime = System.currentTimeMillis();
final GL2 gl = d.getGL().getGL2();
gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
// If we were drawing any other buffers here we'd need to set this every time
// but instead we just leave them bound after initialization, saves a little render time
// No combination of these seems to fix the problem
//gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, geometry.vertexBufferID);
//gl.glVertexAttribPointer(vertexAttribute, 3, GL2.GL_FLOAT, false, 0, 0);
//gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, geometry.indexBufferID);
gl.glBindBuffer(GL2.GL_TEXTURE_BUFFER, geometry.positionBufferID);
//gl.glActiveTexture(GL2.GL_TEXTURE0);
//gl.glTexBuffer(GL2.GL_TEXTURE_BUFFER, GL2.GL_R32F, geometry.positionBufferID);
_render(gl, geometry);
// Also tried these
//gl.glFlush();
//gl.glFinish();
}
public void _render(GL2 gl, SquareGeometry geometry)
{
gl.glMultiDrawElements(geometry.drawMode, geometry.countBuffer, GL2.GL_UNSIGNED_SHORT, geometry.offsetBuffer, NUM_THINGS);
// This one works, but isn't what I want
//gl.glDrawElements(GL2.GL_LINE_LOOP, count, GL2.GL_UNSIGNED_SHORT, 0);
}
public void reshape(GLAutoDrawable d, int x, int y, int width, int height)
{
final GL2 gl = d.getGL().getGL2();
gl.glViewport(0, 0, width, height);
float ratio = (float) height / width;
screenWidth = width;
screenHeight = height;
viewWidth = 100;
viewHeight = viewWidth * ratio;
Matrix.ortho3f(0, viewWidth, 0, viewHeight);
if (!didInit)
{
viewInit(gl);
didInit = true;
}
else
{
// respond to view size changing
}
}
}
The final bit is the SquareGeometry class which holds all the bufferIDs and vertex data, but also is responsible for filling the vertex buffer correctly so that each vertex's z component can function as an index into the position texture:
public class SquareGeometry
{
public float[] vertices = null;
ShortBuffer indexBuffer;
IntBuffer countBuffer;
PointerBuffer offsetBuffer;
FloatBuffer vertexBuffer;
public int vertexBufferID = 0;
public int indexBufferID = 0;
public int positionBufferID = 0;
public int positionTextureID = 0;
public int drawMode;
protected float width = 0;
protected float height = 0;
public SquareGeometry(float size)
{
width = size;
height = size;
}
public void buildGeometry(float viewWidth, float viewHeight)
{
vertices = new float[4 * 2];
vertices[0] = -width/2;
vertices[1] = -height/2;
vertices[2] = -width/2;
vertices[3] = height/2;
vertices[4] = width/2;
vertices[5] = height/2;
vertices[6] = width/2;
vertices[7] = -height/2;
drawMode = GL2.GL_POLYGON;
}
public void finalizeGeometry(int numInstances)
{
if(vertices == null) return;
int num_vertices = this.getNumPoints();
int total_num_vertices = numInstances * num_vertices;
// initialize vertex Buffer (# of coordinate values * 4 bytes per float)
ByteBuffer vbb = ByteBuffer.allocateDirect(total_num_vertices * 3 * Float.SIZE);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
for(int i = 0; i < numInstances; i++)
{
for(int v = 0; v < num_vertices; v++)
{
int vertex_index = v * 2;
vertexBuffer.put(vertices[vertex_index]);
vertexBuffer.put(vertices[vertex_index+1]);
vertexBuffer.put(i);
}
}
vertexBuffer.rewind();
// Create the indices
vbb = ByteBuffer.allocateDirect(total_num_vertices * Short.SIZE);
vbb.order(ByteOrder.nativeOrder());
indexBuffer = vbb.asShortBuffer();
for(int i = 0; i < total_num_vertices; i++)
{
indexBuffer.put((short) (i));
}
indexBuffer.rewind();
// Create the counts
vbb = ByteBuffer.allocateDirect(numInstances * Integer.SIZE);
vbb.order(ByteOrder.nativeOrder());
countBuffer = vbb.asIntBuffer();
for(int i = 0; i < numInstances; i++)
{
countBuffer.put(num_vertices);
}
countBuffer.rewind();
// create the offsets
offsetBuffer = PointerBuffer.allocateDirect(numInstances);
for(int i = 0; i < numInstances; i++)
{
offsetBuffer.put(num_vertices*i*2);
}
offsetBuffer.rewind();
}
public int getNumPoints()
{
return vertices.length/2;
}
}
Ok first things first, you are not setting gl_Color in the shader maybe that can be the issue here and you only lucky with small numbers. It is a varying, but do you also have fragment shader that picks up the value?
At no point do you ensure that NUM_THINGS*2 < GL_MAX_TEXTURE_SIZE. I don't know how FloatBuffer.put reacts; being Java probably / hopefully an exception.
Also you bind the positionBufferID buffer, then unbind it but never rebind it.
You create positionTextureID but never put any data there. This also what you put into the sampler positionSampler and try to access.
Yea well lots of issues but my gut tells me the last one may be the real issue here.
Alright, I've got it solved, though I'm still really not clear on what the original problem was. I fixed it by simplifying the drawing to use drawArrays instead of drawElements or multiDrawElements. I'm really not sure why I thought I needed them, as I really don't in this case. I'm pretty sure I was messing up a few things with the indexes and offsets.
Furthermore, as far as the proper way to bind the texture buffer, neither the code I have above, nor example found at the link I posted in a comment are correct at all.
If anyone is interested in the correct way to use the texture buffer like this I just did a pretty extensive write-up on it here http://zebadiah.me/?p=44. Thanks all for the help.