Processing - rendering shapes is too slow - java

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:

Related

How to make falling object hit certain object in Java Processing

How do you get a certain object to hit another object?
I am making a game where trash is falling from the sky, you are meant to drag and drop the trash into the correct bins. How do I make it so when you drag it into the regional area of the bin is, it gives you a score and resets again. I have made the score & lives function but how do you get it to hit the bin and give you a score?
Here's the progress I have made so far:
PImage scene, can, brownB, blueB, greenB;
int can_x = -100; //CAN IS HIDDEN FOR NOW
int can_y = 500;
int canX = 100;
int canY = 50;
int canwidth = width/2;
int canheight = height/2;
int score = 0;
int lives = 3;
int can_count = 0;
int quit = 0;
boolean mouseInCan = false;
void setup() // Entry point (start of program), runs once
{
size(800,600,P2D); // Create a Window, must be same size as scene
scene = loadImage("backround.png"); // load image and data into scene data structure
can = loadImage("can.png"); // load image of can into the GPU
greenB = loadImage("green_bin.png");
textureMode(NORMAL); // Scale texture Top right (0,0) to (1,1)
blendMode(BLEND); // States how to mix a new image with the one behind it
noStroke(); // Do not draw a line around objects
canX = 0 + (int)random(800); // Choose drop starting position
canY = 0;
}
//check if the mouse is in the can
void mousePressed() {
if (mouseX > canX && mouseX < canX + canwidth && mouseY > canY && mouseY < canY + canheight) {
mouseInCan = true;
}
}
//if the mouse is in the can, then move it when the mouse is dragged
void mouseDragged() {
if (mouseInCan) {
float deltaX = mouseX - pmouseX;
float deltaY = mouseY - pmouseY;
canX += deltaX;
canY += deltaY;
}
}
//when we let go of the mouse, stop dragging the can
void mouseReleased() {
mouseInCan = false;
}
void draw()
{
background(scene); // Display background image referenced by scene
image(can, canX, canY, canwidth, canheight); //Can is drawn at position
image(greenB, 100, 450, width/8, height/4);
pushMatrix(); // Store current location of origin (0,0)
translate(can_x,can_y); // Change origin (0,0) for drawing to (drop_x,drop_y)
beginShape(); // Open graphics pipeline
texture(can); // Tell GPU to use drop to texture the polygon
vertex( -20, -20, 0, 0); // Load vertex data (x,y) and (U,V) texture data into GPU
vertex(20, -20, 1, 0); // Square centred on (0,0) of width 40 and height 40
vertex(20, 20, 1, 1); // Textured with an image of a drop
vertex( -20, 20, 0, 1);
endShape(CLOSE); // Tell GPU you have loaded shape into memory.
popMatrix();
canY += 1.5; // Make "drop" move down the screen (two pixels at a time)
if (canY>600) // If y value is entering the bottom of screen
{
canX = 0+(int)random(800); // Restart the drop again in the cloud.
canY = 0;
lives--;
}
if ((canY>100)&&(canY<450)) // If drop is on same level as player
{
if(abs((canX+100)-(canX+450))<25) // And drop is near player
{
score++; // Increase drop count by one (caught)
canX=0+(int)random(800); // Restart a new drop in the cloud
canY=0;
}
}
textSize(18); // Display score information on the screen
fill(0,0,0);
text("Lives: "+lives, 540, 20);
textSize(18);
fill(0,0,0);
text("Score: "+score, 640, 20);
if(quit==1) // Wait five seconds before exiting
{
delay(5000);
exit();
}
if (lives<1) // All lives lost so game over but
{ // return to draw one more time to
quit = 1; // allow "Game Over to be displayed.
}
}
I have tried doing it where the comment with 'If drop is on same level as player' but no luck, The code is a mess right now but I will clean it up, still new to processing.
What you are looking for is some kind of collision detection. There are very complex algorithms out there that can do an accurate detection with complex shapes, at the cost of CPU time, but in your case, just checking the intersection of two rectangles will probably be enough, at least for a start.
First you need to have your bin and can's positions and dimensions stored somewhere. We'll use the enclosing rectangle of each as a collision box.
PImage scene, can, brownB, blueB, greenB;
int canX = 100, canY = 50, canwidth = 80, canheight = 80;
int binX = 100, binY = 450, binW = 100, binH = 150;
You can then adjust the dimensions according to your screen size, but it has to be done after you have initialized it in setup:
void setup() // Entry point (start of program), runs once
{
size(800, 600, P2D); // Create a Window, must be same size as scene
scene = loadImage("backround.png"); // load image and data into scene data structure
can = loadImage("can.png"); // load image of can into the GPU
greenB = loadImage("green_bin.png");
//...
canwidth = width / 10;
canheight = width / 10;
binW = width / 8;
binH = height / 4;
binY = height - binH; //Set the bin at the bottom of the screen
}
Now your elements can be drawn according to the stored values, how and where you intend them, and you can do your collision detection inside your draw loop:
void draw()
{
background(scene); // Display background image referenced by scene
image(can, canX, canY, canwidth, canheight); //Can is drawn at position
image(greenB, binX, binY, binW, binH);
//...
/* If the top of the can is below the top of the bin and the can is
* horizontally within the bin, then we count score
*/
if (canY > binY && canX > binX && canX + canwidth < binX + binW) {
score++; // Increase drop count by one (caught)
canX=0+(int)random(800); // Restart a new drop in the cloud
canY=0;
}
//...
}
I've neglected the check for bottom coordinates of the can relative to the bin, as the bin is at the very bottom, and hitting the bottom kills you.
As was said already, you need some kind of collision detection. I would abstain though from trying to implement any algorithms myself and would instead use a professional library for that like:
https://github.com/locationtech/jts
That way you can also test against the real geometry and the overhead is minimal because these tests are first run againt the bounding boxes and only when that is positive they are refined to the real geometry. In addition you could also use a spatial index to speed up the search.

Processing - Zoom on clicked area then click again to zoom back out

I have a picture then used a flashlight type of light to only show where the mouse is hovering over. That part of the code works, but now I want to use if/else statements to zoom in on the selected area and then click again to zoom back out. Any other way to zoom in on specific area then back out of that area also helps. Really any help will be appreciated!
PImage ispy;
void setup () {
size(1024,768);
ispy = loadImage("ispy2.jpeg");
}
void draw () {
loadPixels();
ispy.loadPixels();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int loc = x+y*width;
float r = red(ispy.pixels[loc]);
float g = green(ispy.pixels[loc]);
float b = blue(ispy.pixels[loc]);
float d = dist(mouseX, mouseY, x, y); //
float factor = map(d, 0, 200, 2, 0);
pixels[loc] = color(r*factor, g*factor, b*factor);
}
}
updatePixels();
}
Here is my interpretation of what you are talking about. We store a isClicked boolean to store the state of whether we should zoom in or not. When we are going to draw the image, we translate() to the mouse, then we scale(), then we translate() back the same amount that we moved before, but in the opposite direction. What this does is it does the scale transform around the mouse position.
One thing that I couldn't find a way around way your way of updating the pixels directly from the image and the flashlight effect. What the program is doing instead is using your method to make a mask image and applying that to a PGraphics object. Another thing that I noticed is that when just rendering straight to the screen, there is considerable lag from the scaling. Instead, I have moved the drawing to a PGraphics object. This improves the performance.
In the end, to render, the program is drawing everything on the PGraphics object, then applying the mask to that object to get the flashlight effect.
Here is the code that I have:
PImage ispy, distMask;
boolean isClicked = false;
PGraphics renderer;
void createDistanceMask(PImage distMask){ //Takes image and changes its pixels to "alpha" for the PGraphics renderer
distMask.loadPixels();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int loc = x+(height-y-1)*width;
float d = dist(mouseX, mouseY, x, y); //
int factor = int(map(d, 0, 200, 400, 0)); //Pixel data will be between 0 and 255, must scale down later.
if (factor > 255)
factor = 255;
distMask.pixels[loc] = color(factor,factor,factor);
}
}
distMask.updatePixels();
}
void setup () {
size(1024,768, P2D);
ispy = loadImage("ispy2.jpeg");
distMask = new PImage(width,height);
renderer = createGraphics(width,height,P2D);
mouseX = width/2; //Not necessary, but will have black screen until mouse is moved
mouseY = height/2;
}
void draw () {
background(0);
pushMatrix();
createDistanceMask(distMask);
renderer.beginDraw(); //Starts processing stuff into PGraphics object
renderer.background(0);
if(isClicked){ //This is to get the zoom effect
renderer.translate(mouseX, mouseY);
renderer.scale(2);
renderer.translate(-mouseX, -mouseY);
}
renderer.image(ispy,0,0); //Render Image
renderer.endDraw();
renderer.mask(distMask); //Apply Distance mask for flashlight effect
image(renderer,0,0); //Draw renderer result to screen
popMatrix();
}
void mouseClicked(){
isClicked = !isClicked;
}
In my comment, I asked about having the screen move to the mouse, which is what this is doing. If you want to "freeze" the screen in one position, what you can do is store a lastMouseClickPosition PVector or simply just ints. Then, when translating, translate to the position instead of the PVector.
Here's the code that would change:
PVector lastClickPos = new PVector(); //Make the position
if(isClicked){ //When Rendering
renderer.translate(lastClickPos.x, lastClickPos.y);
renderer.scale(scalingFactor);
renderer.translate(-lastClickPos.x, -lastClickPos.y);
}
void mouseClicked(){ //At the bottom
isClicked = !isClicked;
lastClickPos.set(mouseX, mouseY);
}

javafx update pixels every frame

For a project I'm working on, I need to animate an ocean through salinity colormaps. Essentially, I have salinity data for n timesteps and I need to update the pixel colors to match the current salinity values.
Right now I'm going through every pixel on a canvas in a nested for-loop and, for each pixel, computing the current salinity value at that pixel and updating the pixel's color to match the salinity value.
I keep track of the frames with a AnimationTimer object. The problem is that I'm getting around 20 fps and the application is lagging on events like resizing the window.
private void drawHeatMap(int depth, float t) {
float meanSalt = model.getMeanSalinity(depth);
float stdSalt = model.getSalinityStdDev(depth);
Colorbar colorbar = new Colorbar(SALINITY_COLORMAP);
colorbar.setValueRange(meanSalt - stdSalt, meanSalt + (stdSalt / 2));
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
PixelWriter pixelWriter = graphicsContext.getPixelWriter();
for (int x = 0; x < canvas.getWidth(); ++x) {
for (int y = 0; y < canvas.getHeight(); ++y) {
GeoCoordinate coordinate = getMapCoordinates(x, y);
if (model.isOverWater(coordinate)) {
float salt = model.computeSalinity(coordinate, depth, t);
pixelWriter.setColor(x, y, colorbar.getColor(salt));
}
}
}
}
This is the code that runs every frame, but even a simpler version like the one below still runs at 30 fps.
private void drawHeatMapBenchmark(int depth, float t) {
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
PixelWriter pixelWriter = graphicsContext.getPixelWriter();
for (int x = 0; x < canvas.getWidth(); ++x) {
for (int y = 0; y < canvas.getHeight(); ++y) {
pixelWriter.setColor(x, y, Color.gray(Math.random()));
}
}
}
Anyone knows a better approach to this? I'm not familiar with computer graphics, thank you.
You are doing all this on the (single) JavaFX application thread. It might help if you first render your heat map into an image which you then draw onto the canvas because for your particular case it seems to be very simple to do all the image rendering on parallel threads which should speed up the whole process.

Android - Canvas onDraw with zoomed camara following character movements

Some Context:
I am creating an Android game which draws a maze onto a canvas against a background image, this uses a square block and therefore the maze is always automtically zoomed in to a 5 x 5 square, of a maze that may be 20 x 20. The maze is drawn by simply running through a set of for loops and then drawing lines in the relevent places. This is all working perfectly, however I am having an issue with getting my onDraw to work smoothly. This is occuring due to the fact that everytime I invalidate I have to re-run the for look and run various if statements to check positions (unfortunetly this process cannot be improved).
The Question:
I am looking to re-write the way my maze is drawn onto the canvas, below are the 3 main features I need to implement:
1 - Draw the whole maze onto the canvas (this is easy enough)
2 - Zoom in on the maze so only a 5 x 5 is shown
3 - Move the character (who is always centered on the screen) and draw the next seciton of the maze
Now as mentioned above drawing the whole maze is easy enough and will make the onDraw signifcatly quicker as their is no need to run the for loop on invaldate, it can be done once when the level is first loaded up.
In terms of point 2 & 3, the way I see this working is the charcacter to be drawn in the middle of the canvas then a 2d birdseye view camera to be attached / linked to the characters movement. This camera would also need to be zoomed in to an extent that it only displays a 5 x 5 of the overall maze grid. Then as the charcater moves the camera moves with the character and displays the next section of the maze which has already been drawn. I have tried a few things and done some reasearch however I have never worked with canvas before and no idea really where to start and if this is even possible.
So to sum up the main part of the question is whether their is a way to link a birdseye view camera to a character which is zoomed in and moves with the character image.
Below is a snippet as to how the maze is currently drawn, using 2 sets of for loops checking against 2 sets of boolean arrays[][], which simply store the vertical and horixaonl lines to be drawn.
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawRect(0, 0, width, 100, background);
RectBackground.set(0,0, FullScreenWidth, FullScreenWidth);
canvas.drawBitmap(gameBackground, null, RectBackground, null);
for(int i = 0; i < 5; i++)
{
for(int j = 0; j < 5; j++)
{
float x = j * totalCellWidth;
float y = i * totalCellHeight;
indexY = i + currentY;
indexX = j + currentX;
// Draw Verticle line (y axis)
if (indexY < vLength && indexX < vLines[indexY].length && vLines[indexY][indexX])
{
RectOuterBackground.set((int)x + (int)cellWidth, (int)y, (int)x + (int)cellWidth + 15, (int)y + (int)cellHeight + 15);
canvas.drawBitmap(walls, null, RectOuterBackground, null);
}
// Draws Horizontal lines (x axis)
if (indexY < hLength && indexX < hLines[indexY].length && hLines[indexY][indexX])
{
RectOuterBackground.set((int)x, (int)y + (int)cellHeight,(int)x + (int)cellWidth + 15,(int)y + (int)cellHeight + 15);
canvas.drawBitmap(walls, null, RectOuterBackground, null);
}
}
}
}
To make the drawing faster, you can double buffer the canvas by drawing directly into a bitmap and flashing the bitmap into the canvas like this. It's very fast.
private void init()
{
//variables below are class-wide variables
b = Bitmap.createBitmap(cwidth, cheight, Bitmap.Config.ARGB_8888);
bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmapPaint.setStyle(Paint.Style.STROKE);
bitmapPaint.setStrokeWidth(3);
myPaint.setColor(0xff000000);
myCanvas = new Canvas(b);
}
protected void onDraw(Canvas c)
{
super.onDraw(c);
c.drawBitmap(b, 0, 0, myPaint);
for(int i = 0; i < number lines; i++)
{
//draw here using myPath
}
if(b != null)
c.drawBitmap(b, 0, 0, myPaint);
myCanvas.drawPath(myPath, bitmapPaint);
}
Then, to "move around" I would suggest using a crop box. What this means is that a 1:1 scale, the image is larger than the viewport displayed on the screen. Really what's happening when someone "moves" is the bitmap is being moved beneath the crop box.
You could use BitmapRegionDecoder and display only the region the character is in (this might be slowish) or by using
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
The src parameter here allows you to specify a small region of the bitmap to display. It is effectively a crop box.
To zoom in and out you need to multiply the coordinates by a number( zoom factor)
int x1 = (int)x + (int)cellWidth;
int y1 = (int)y;
int x2 = (int)x + (int)cellWidth + 15;
int y2 = (int)y + (int)cellHeight + 15;
RectOuterBackground.set(x1*zoom, y1*zoom, x2*zoom, y2*zoom);
note that zoom must be a floating point number, using zoom=2 that makes everything twice as big.
if you want to put the camera on top of the cell (xc, yc) you need to do this:
RectOuterBackground.set( (x1-xc)*zoom, (y1-yc)*zoom, (x2-xc)*zoom, (y2-yc)*zoom);
Try first drawing the whole maze, and soon you will figure out how to only draw the bit of the maze that is only inside the screen
I hope this helps and let me know if you have any questions :)

How to speed up my games fps?

I have my game which, on every render loop, loops through all the blocks in my map (128x128) which as you can probably tell, causes a lot of lag. When I first made it, I had to make it render only the blocks on the screen, or it would crash instantly. Now I only render the blocks on the screen, but still loop through all the blocks to see if they are on the screen, which makes my fps about 2.
for (int y = 0; y < 128; y++) {
for (int x = 0; x < 128; x++) {
Block b = LevelGen.getBlockAt(x, y);
if (Forgotten.isInsideGameWindow(x * 30, y * 30)) {
arg1.drawImage(b.getTexture(), x * 30, y * 30);
}
}
}
Is there a way to make it so doesn't loop through all of them?
Figure out the size of your display window, and only iterate blocks that are within it.
final int tile_size = 30;
final RectangleType displayWindow = Forgotten.getDisplayWindow ();
final int left = displayWindow.getLeft () / tile_size - 1;
final int right = displayWindow.getRight () / tile_size + 1;
final int top = displayWindow.getTop () / tile_size - 1;
final int bottom = displayWindow.getBottom () / tile_size + 1;
for (int y = top; y < bottom; ++y)
for (int x = left; x < right; ++x)
canvas.drawImage (LevelGen.getBlockAt (x,y).getTexture (),
x * tile_size, y * tile_size);
You may also want to figure out which area(s) of the canvas actually want to be drawn, and instead keep a "dirty rectangle" list of areas to be redrawn. Whenever a tile changes, or a sprite/particle/whatever passes through its space, add that tile to the rectangle. Even if you just use a single dirty rectangle that enlarges to encompass all updates during a frame, if your game doesn't actually have "stuff happening" at all points on the display at all times, your frame rate will be higher on average (but suffer from large-scale effects)
expanding upon that:
public class Map {
private Rectangle dirtyRect = null;
public void dirty (Rectangle areaAffected) {
if (null == dirtyRect) {
dirtyRect = areaAffected; return;
}
dirtyRect = new Rectangle ( Math.min (dirtyRect.getLeft (),
areaAffected.getLeft () ),
Math.min (dirtyRect.getTop (),
areaAffected.getTop () ),
Math.max (dirtyRect.getRight (),
areaAffected.getRight () ),
Math.max (dirtyRect.getBottom (),
areaAffected.getBottom () ));
}
Then use the dirty rectangle in place of displayWindow for normal draws, and you can test if (null == getDirtyRectangle ()) return; to skip drawing at all if nothing's changed.
You could just insert a break inside of your code block.. but then I don't see the purpose of this code looking through 16, 32, or 256 times...
Well, you could place all the blocks in an array, include coordinates in the block object then use those to draw the block. Then you'd only have to loop through the blocks that appear on screen. Only useful if there aren't that many blocks on screen though.

Categories