So I'm rendering a tilemap (simply a twodimensional array with MapTiles), called WorldGeneration, using a isometric renderer class I've created. The camera I'm using uses 64 pixels per meter, so its height for example is: (game window height)/64. The SpriteBatch used in the isometric renderer has it's projection matrix set to this camera. (batch.setProjectionMatrix(cam.combined)
The problem is, I'm getting horrible performance when rendering textures onto this SpriteBatch. I'm not sure if it's caused by the projection matrix being what it is (needing to scale my 64x64 textures to 1x1 in batch dimensions), or something else.
I've tried running the program without executing the batch.draw call in the renderer (see code below) and everything works fine. Is anyone able to find the problem or give me some tips?
Relevant code snippets:
Render method of the isometric renderer:
public void render(WorldGeneration gen) {
//TODO compensate for elevation
int x1 = (int) (cameraIso.x - 23); // TODO compensate for zoom
int x2 = (int) (cameraIso.x + 23);
int y1 = (int) (cameraIso.y - 23);
int y2 = (int) (cameraIso.y + 23);
if(x1 < 0){
x1 = 0;
}else if(x1 >= gen.getLengthX()){
x1 = gen.getLengthX() - 1;
}
if(y1 < 0){
y1 = 0;
}else if(y1 >= gen.getLengthY()){
y1 = gen.getLengthY() - 1;
}
if(x2 < 0){
x2 = 0;
}else if(x2 >= gen.getLengthX()){
x2 = gen.getLengthX() - 1;
}
if(y2 < 0){
y2 = 0;
}else if(y2 >= gen.getLengthY()){
y2 = gen.getLengthY() - 1;
}
batch.begin();
for (int x = x2; x >= x1; x--) {
for (int y = y2; y >= y1; y--) {
tiles = gen.getTiles(x, y);
if(tiles == null){
continue;
}else if(tiles.size <= 0){
continue;
}
for(MapTile tile: tiles){
if(tile.getOpacity() < 1){
batch.setColor(batchColor.r, batchColor.g, batchColor.b, tile.getOpacity());
}else{
batch.setColor(batchColor.r, batchColor.g, batchColor.b, 1);
}
//TODO only visible (not underneath several tiles)
pos.x = x;
pos.y = y;
//As you can see the texture is scaled to 1x1 because of batch:
batch.draw(textures[tile.getId()], translateCartToIso(pos).x,
translateCartToIso(pos).y + tile.getZ(), 1f, 1f);
}
}
}
batch.end();
}
As you can see I've even created the Vector2 pos outside the render method to increase performance, but I'm not sure if that's even necessary. It is also worth noting that tiles are locked to a xy grid, but their z value is not, that is why an array is needed.
Edit: Somehow the performance is great if the camera is zoomed out, have no idea why this is. It definitely has something to do with the camera dimensions.
Found the answer, it actually had to do with the graphics behind everything. I noticed that when I ran stress tests my computer (mac) would shut down because of a graphics problem.
It basically came down to this line:
cfg.useHDPI = true;
Where I made my LwjglApplicationConfiguration use HD pixels, I guess my computer couldn't handle it when it had to process too many HD pixels (that's why it worked when i zoomed the camera out, less pixels)
I guess I'll have to deal with some distorted pixels for now, that's a problem I'll have to solve later!
I'm not exactly positive on everything your code is doing, but it looks like the outermost loop has a runtime of O(56), the inner for loop has another O(56) runtime, and then the innermost foreach loop has a runtime of O(<# of elements in tiles>) giving you a grand total of: around 3,000 draw calls, assuming one element in tiles.
That's already quite a bit of drawing, not to mention the performance decrease scaling can often impart. Is there no way to optimize that?
Related
I have been doing a small little project using Processing, and the effect I wanted to achieve was a kind of "mountains" forming and moving, using Perlin Noise with the noise() function, with 2 parameters.
I was originally using a image for the background, but for illustrational purposes, I made the background black, and it's basically the same effect.
My issue is that I want to have a "history" of the mountains because they should fade away after some time, and so I made a history of PShapes, and draw the history and update it each frame.
Updating it is no issue, but drawing the PShapes seems to take a lot of time, reducing the frame rate from 60 to 10 when the length of the history is 100 elements.
Below is the code I used :
float noise_y = 0;
float noise_increment = 0.01;
// increment x in the loop by this amount instead of 1
// makes the drawing faster, since the PShapes have less vertices
// however, mountains look sharper, not as smooth
// bigger inc = better fps
final int xInc = 1;
// maximum length of the array
// bigger = less frames :(
final int arrLen = 100;
int lastIndex = 0;
PShape[] history = new PShape[arrLen];
boolean full = false;
// use this to add shapes in the history
PShape aux;
void setup() {
size(1280, 720);
}
void draw() {
background(0);
// create PShape object
aux = createShape();
aux.beginShape();
aux.noFill();
aux.stroke(255);
aux.strokeWeight(0.5);
for (float x = 0; x < width + xInc; x = x + xInc) {
float noise = noise(x / 150, noise_y) ;
// get the actual y coordinate
float y = map(noise, 0, 1, height / 2, 0);
// create vertex of shape at x, y
aux.vertex(x, y);
}
aux.endShape();
// push the current one in the history
history[lastIndex++] = aux;
// if it reached the maximum length, start it over ( kinda works like a queue )
if (lastIndex == arrLen) {
lastIndex = 0;
full = true;
}
// draw the history
// this part takes the MOST TIME to draw, need to fix it.
// without it is running at 60 FPS, with it goes as low as 10 FPS
if (full) {
for (int i = 0; i < arrLen; i++) {
shape(history[i]);
}
} else {
for (int i = 0; i < lastIndex; i++) {
shape(history[i]);
}
}
noise_y = noise_y - noise_increment;
println(frameRate);
}
I have tried to use different ways of rendering the "mountains" : I tried writing my own class of a curve and draw lines that link the points, but I get the same performance. I tried grouping the PShapes into a PShape group object like
PShape p = new PShape(GROUP);
p.addChild(someShape);
and I got the same performance.
I was thinking of using multiple threads to render each shape individually, but after doing some research, there's only one thread that is responsible with rendering - the Animation Thread, so that won't do me any good, either.
I really want to finish this, it seems really simple but I can't figure it out.
One possible solution would be, not to draw all the generated shapes, but to draw only the new shape.
To "see" the shapes of the previous frames, the scene can't be cleared at the begin of the frame, of course.
Since the scene is never cleared, this would cause, that the entire view is covered, by shapes over time. But if the scene would be slightly faded out at the begin of a new frame, instead of clearing it, then the "older" shapes would get darker and darker by time. This gives a feeling as the "older" frames would drift away into the depth by time.
Clear the background at the initlization:
void setup() {
size(1280, 720);
background(0);
}
Create the scene with the fade effect:
void draw() {
// "fade" the entire view
blendMode(DIFFERENCE);
fill(1, 1, 1, 255);
rect(0, 0, width, height);
blendMode(ADD);
// create PShape object
aux = createShape();
aux.beginShape();
aux.stroke(255);
aux.strokeWeight(0.5);
aux.noFill();
for (float x = 0; x < width + xInc; x = x + xInc) {
float noise = noise(x / 150, noise_y) ;
// get the actual y coordinate
float y = map(noise, 0, 1, height / 2, 0);
// create vertex of shape at x, y
aux.vertex(x, y);
}
aux.endShape();
// push the current one in the history
int currentIndex = lastIndex;
history[lastIndex++] = aux;
if (lastIndex == arrLen)
lastIndex = 0;
// draw the newes shape
shape(history[currentIndex]);
noise_y = noise_y - noise_increment;
println(frameRate, full ? arrLen : lastIndex);
}
See the preview:
I am trying to make holdable items for my slick2d game, I figured a good way to do this would be to have a hand on the player, to do this i have 1 pixel a unique colour, allowing me to locate that colour, and the x +y.
It worked perfectly until i tried to scale up the image and i get this crazy out of bounds exeception.
this is my code to find the x and y:
public int[] getLocation(Image i, Color c){
Image scaled = i.getScaledCopy(getWidth(), getHeight());
for(int x = 0; x < scaled.getWidth(); x++){
for(int y = 0; y < scaled.getHeight(); y++){
if(scaled.getColor(x, y).r == c.r && scaled.getColor(x, y).g == c.g && scaled.getColor(x, y).b == c.b){
int[] xy = {x,y};
return xy;
}
}
}
return null;
}
and this is how i use it
float x = (float) (getLocation(walkLeft.getCurrentFrame(), new Color(1, 1, 1))[0] + getX());
float y = (float) (getLocation(walkLeft.getCurrentFrame(), new Color(1, 1, 1))[1] + getY());
g.fillRect(x, y, 2, 2);
the exception is:
java.lang.ArrayIndexOutOfBoundsException: 16384
and it leads me back to this line:
if(i.getColor(x, y).r == c.r && i.getColor(x, y).g == c.g && i.getColor(x, y).b == c.b){
in the getLocation method..
I have a feeling its dead easy, yet its stumped me. Thanks to anyone to responds.
The loop(s) in getLocation loop over the dimensions of a 2x scaled copy of the Image, but then attempt to access the pixels of the original. Given the original is half the size, when you are half-way through the loop you will be out of bounds of the image dimensions. Either:
Don't scale the Image
If you must scale the Image, check the pixel value of the scaled image rather than the original.
As an aside, the code posted contains redundant calls...a) in getLocation if you are going to scale, consider scaling the image once rather than placing that code within the loop itself b) no need to call getLocation twice with the same parameters. Call it once and just use the returned array
I'm making a program that deals with a large collection of 2D data. I'm displaying this data in an XYPlot. Is there some way I can mark which direction the line is moving over time? The data bounces all over the plot, but needs to be in a specific order, so a scatter plot won't work for me.
EDIT: To clarify - Neither the X or Y axis is time. This is a parametric plot.
I have discovered a possible solution. It needs some working through to make it look right, but it works for now.
There is a class in JFreeChart called XYPointerAnnotation, which allows you to add arrows to your chart of a custom angle, length, and width. I'm still trying to figure the class out, but so far it seems to do well enough for my purposes. Code attached:
for (int i = 0;i < data.getItemCount(0) - 1;i++)
{
double x1 = data.getSeries(0).getDataItem(i).getXValue();
double x2 = data.getSeries(0).getDataItem(i + 1).getXValue();
double y1 = data.getSeries(0).getDataItem(i).getYValue();
double y2 = data.getSeries(0).getDataItem(i + 1).getYValue();
double angle = Math.atan2(y1 - y2, x2 - x1) + Math.PI;
XYPointerAnnotation arrow = new XYPointerAnnotation("",x1,y1,angle);
if (i == 0)
{
arrow.setText("Start");
}
else if (i % 5 == 0)
{
arrow.setText(Integer.toString(i));
}
arrow.setLabelOffset(15.0);
arrow.setToolTipText(Integer.toString(i));
_originalPlot.addAnnotation(arrow);
}
Ok, so I have a game I just started and I am kind of stuck on the smooth scrolling. I have the basic scrolling part done but my background (Grid) only moves by intervals of 50.
for (int x = (getPlayerX() / getTileSize()) - 6; x < (getPlayerX() / getTileSize()) + 9; x++)
{
for (int y = (getPlayerY() / getTileSize()) - 5; y < (getPlayerY() / getTileSize()) + 8; y++)
{
int xPos = ((x - (getPlayerX() / tileSize)) + (getScreenX() / tileSize) - 1) * tileSize;
int yPos = ((y - (getPlayerY() / tileSize)) + (getScreenY() / tileSize) - 1) * tileSize;
if (x > 0 && x < mapX && y > 0 && y < mapY)
{
if (getTiles()[x][y].tileID == 0)
{
g.drawRect(xPos, yPos, tileSize, tileSize);
}
if (getTiles()[x][y].tileID == 1)
{
g.fillRect(xPos, yPos, tileSize + 1, tileSize + 1);
}
}
}
}
Sorry about the subtraction and addition in the for loops, I have them set up so it will display from 1 to whatever instead of 0 to whatever - 1.
So basically I want to redraw the grid every pixel I move, instead of every 50. Put I dont want to iterate over every pixel on the screen
Ok, at first you are confusing with the background and the grid. A grid is a way to check collisions and a spatial partitioning algorithm that never moves. A background is an image which is drawn behind every entity in your game in every level.
What you are trying to do is make the level scroll, as per what I understand. To do so, we create a class called View also called as a Camera which draws the background and the visible entities by centering the entity which is controlled by the player.
And to center an entity and draw the map, I use these classes. Hope you understand that I wrote them in C#.
Map
MapInfo
MapLayer
MapLoader
MapManager
MapView
And here is my implementation of Grid. I have the java versions of the same classes here but I haven't implemented the Grid class in java.
You can read my tutorial Using Grids For Collisions for more info on how it works. Hope it helps.
I'm attempting to play with graphics using Java/Slick 2D. I'm trying to get my sprite to rotate to wherever the mouse is on the screen and then move accordingly.
I figured the best way to do this was to keep track of the angle the sprite is at since I have to multiply the cosine/sine of the angle by the move speed in order to get the sprite to go "forwards" even if it is, for instance facing 45 degrees in quadrant 3.
However, before I even worry about that, I'm having trouble even getting my sprite to rotate in the first place.
Preliminary console tests showed that this code worked, but when applied to the sprite, it just kind twitches. Anyone know what's wrong?
int mX = Mouse.getX();
int mY = HEIGHT - Mouse.getY();
int pX = sprite.x;
int pY = sprite.y;
int tempY, tempX;
double mAng, pAng = sprite.angle;
double angRotate = 0;
if (mX != pX) {
tempY = pY - mY;
tempX = mX - pX;
mAng = Math.toDegrees(Math.atan2(Math.abs((tempY)), Math.abs((tempX))));
if (mAng == 0 && mX <= pX)
mAng = 180;
}
else {
if (mY > pY)
mAng = 270;
else
mAng = 90;
}
// Calculations
if (mX < pX && mY < pY) { // If in Q2
mAng = 180 - mAng;
}
if (mX < pX && mY > pY) { // If in Q3
mAng = 180 + mAng;
}
if (mX > pX && mY > pY) { // If in Q4
mAng = 360 - mAng;
}
angRotate = mAng - pAng;
sprite.angle = mAng;
sprite.image.setRotation((float) angRotate);
Firstly, atan2 can get the correct angle for you - just remove Math.abs from the inputs, and you won't need your three four if statements that you use to correct the quadrants of the angle. (Though you have to get the subtractions the right way around)
Secondly, you're setting the sprite's rotation to mAng - pAng, which amounts to "old angle - new angle". So in reality you're setting the rotation to how much the angle changed since last time (which makes no sense for this purpose), and not the angle itself.
Combining these suggestions I'd recommend something like this:
mAng = Math.toDegrees(Math.atan2(mY - pY, mX - pX));
sprite.angle = mAng;
sprite.image.setRotation((float) mAng);