Javafx canvas content disappears - java

I'm trying to visualize the process of solving the travelling salesman problem using the 2-opt-change algorithm but sometimes it seems like the execution of the program gets stuck and the window freezes without any error.
Sometimes even the lines drawn on a canvas disappear.
Some help would be nice.
Here's the drawing code:
int numCities = getNumOfCities();
Order currentOrder = data.getCurrentOrder();
if (newValue) {
GraphicsContext gc = canvasCities.getGraphicsContext2D();
gc.clearRect(0, 0, canvasCities.getWidth(), canvasCities.getHeight());
gc.setStroke(new Color(0, 0, 0, 1));
gc.setLineWidth(1);
gc.setFill((Color.GRAY));
for (int i = 0; i < currentOrder.getSize(); i++) {
City current = data.getCities()[currentOrder.getValue(i)];
City next;
if ((i + 1) > numCities - 1) {
next = data.getCities()[currentOrder.getValue(0)];
} else {
next = data.getCities()[currentOrder.getValue(i + 1)];
}
gc.fillOval(current.getX() - 5, current.getY() - 5, 10, 10);
gc.strokeLine(current.getX(), current.getY(), next.getX(), next.getY());
}
if(!(data.getBestEver()==null)) {
Order best = data.getBestEver();
gc.setStroke(new Color(1, 0, 0, 1));
gc.setLineWidth(3);
gc.setFill((Color.GRAY));
for (int i = 0; i < best.getSize(); i++) {
City current = data.getCities()[best.getValue(i)];
City next;
if ((i + 1) > numCities - 1) {
next = data.getCities()[best.getValue(0)];
} else {
next = data.getCities()[best.getValue(i + 1)];
}
gc.fillOval(current.getX() - 5, current.getY() - 5, 10, 10);
gc.strokeLine(current.getX(), current.getY(), next.getX(), next.getY());
}
}
repaint.set(false); //boolean that indicated if something changed and a repaint is necessairy
}

but sometimes it seems like the execution of the program gets stuck
and the window freezes without any error.
JFX consumes lots of resources...especially with Canvas and Shapes (e.g. recursive Group, etc.) If it got stuck it could be the "stack" was on the brink of overflow. Try with -Xss[nnM] where nn is any number, M stands for Megabytes. Example -Xss16M : Stack with 16MB for JFX threads. If it still behaves "weirdly" then combine -Xss with -Xms (for the heap).

The Problem was the multithreading part. After converting the paint part into a runnable that was invoked using Platform.runLater() it works fine.
Thank's to all who spent time on thinking about my problem.

Related

Java/Processing: A* graph node based game

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!

Processing - rendering shapes is too slow

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:

JavaFX Timeline Animation Stutters after a couple hours

Preface:
So I have this, well its not really all that important what the application is, it's a tcp listener that chats it up all day with some hardware. But it's also got some display stuff going on. The display shows different custom nodes in different areas of the screen and at the bottom of the screen is a grid that displays some "stuff" but when there isn't any "stuff" it scrolls some text across the screen.
Question:
As I will post in my method that builds the timeline, there is a Text element that transitions from right to left across the screen. This is working as expected for the first hour or maybe two, but after a while it becomes progressively more jittery. The TCP listener is still snappy and the threads it spawns don't have any trouble going in and out of the controller and changing the UI but that scrolling text is jumping like 2 inches at a time across the 55" screen it is displaying on. I really need a way to get text to scroll smoothly like it does when I first start up the application for an indefinite amount of time.
private void buildChyron() throws IllegalArgumentException {
setMessages();
LowerGrid.getChildren().clear();
StringBuilder txtstr = new StringBuilder();
for (String s : messages) {
txtstr.append(s).append(" ");
}
txtstr = new StringBuilder(txtstr.toString().trim());
Text msg = new Text(txtstr.toString());
LowerGrid.add(msg, 0, 0);
msg.setTextOrigin(VPos.TOP);
msg.setFont(Font.font("Veranda", FontWeight.BOLD, 150));
double sceneWidth = MainGrid.getWidth();
double msgWidth = msg.getLayoutBounds().getWidth();
KeyValue initKeyValue = new KeyValue(msg.translateXProperty(), sceneWidth);
KeyFrame initFrame = new KeyFrame(Duration.ZERO, initKeyValue);
KeyValue endKeyValue = new KeyValue(msg.translateXProperty(), -1.0 * msgWidth);
KeyFrame endFrame = new KeyFrame(Duration.seconds((msgWidth + sceneWidth) / messageRate), endKeyValue);
if ((timeline != null) && (timeline.getStatus().equals(Timeline.Status.PAUSED))) {
timeline.playFromStart();
} else {
timeline = new Timeline(initFrame, endFrame);
timeline.setCycleCount(Timeline.INDEFINITE);
if (msgIndex == messages.size() - 1) msgIndex = 0;
else msgIndex++;
timeline.playFromStart();
}
}
Here is where I call the thing from, as you can see I've been trying to experiment with it thinking maybe it was a hardware resources issue, but I haven't been able to get it under control. I'm testing the thing now and it's in a super visible position in the facility I am at. Super embarassing
private void updateStack() {
int count = 0;
System.out.println(andonStack);
andonStack.sortStack();
LowerGrid.getChildren().clear();
if (pokeyStack.size() > 0) {
AndonPane pane = new AndonPane(pokeyStack.get(0).getPokayo());
LowerGrid.add(pane, 0, 0, 9, 1);
} else {
int i;
for (i = 0; i < andonStack.size(); i++) {
if (i >= 9) break;
panes[i] = new AndonPane(((AndonSignalStack.AndonSignal) andonStack.get(i)).getType(),
((AndonSignalStack.AndonSignal) andonStack.get(i)).getStation(), this);
count++;
}
for (; i < 9; i++) {
panes[i] = new Pane();
panes[i].setStyle("-fx-background-color: rgba(0,0,0, 0)");
panes[i].setPrefWidth(MainGrid.getWidth() * (9 - i) / 9);
}
for (i = 0; i < count; i++) {
LowerGrid.add(panes[i], i, 0);
}
if (i == 0) {
Platform.runLater(this::buildChyron);
}else{
if (timeline != null) {
System.gc();
timeline.stop();
timeline = null;
}
}
}
}
Scrolling Text Picture
the answer was the clear method in the pane that it's setting in. the animation needs to be stopped and deleted
don't use pane.clear(); when theres an animation use LowerGrid.getChildren().removeAll();

Why is my stack overflowing when I push less data through it?

I am working on creating a weighted US map and I have broken the zip codes down into 3 digit zip zones. I then use a for loop that iterates 1000 times to color each 3 digit zip zone a random color.
This works every time with out issue. My current issue is coming in if I start my for loop count above 310. Anything less than 310 and it loops through perfectly. So since raising the initial count would mean that it would run the recursive code less, then this does make any sense to me.
The code that calls the for loop:
private void GUI()
{
JFrame frame = new JFrame();
frame.setLayout(new MigLayout());
try
{
mapImg = ImageIO.read(new File("Res/Zipzone map of the US.png"));
}
catch (IOException e)
{
e.printStackTrace();
}
g = mapImg.createGraphics();
for(int i = 311; i < 1001; i++)
{
Random rand = new Random();
String count = "";
int red = rand.nextInt(220) + 25;
int green = rand.nextInt(220) + 25;
int blue = rand.nextInt(220) + 25;
if(i < 100)
{
count = "0" + i;
}
else
{
count = i + "";
}
if(i <= 512)
{
ApplyColor(count, new Color(red, blue, green));
}
else if( i > 909)
{
ApplyColor3(count, new Color(red, blue, green));
}
else
{
ApplyColor2(count, new Color(red, blue, green));
}
}
frame.add(new JLabel("", new ImageIcon(GetScaledImage(new ImageIcon(mapImg).getImage(), 1400, 875)), JLabel.CENTER), "GROW, PUSH");
frame.setTitle("US Map");
frame.setSize(1500,900);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
Small example of the apply color function:
private void ApplyColor(String zip, Color color)
{
int x;
int y;
if(zip.equals("010"))
{
try
{
x = 3339;
y = 672;
FloodFill(x, y, new Color(mapImg.getRGB(x, y)), color);
x = 3361;
y = 681;
FloodFill(x, y, new Color(mapImg.getRGB(x, y)), color);
}
catch(AWTException e)
{
e.printStackTrace();
}
}
}
And the FloodFill function:
public void FloodFill(int x, int y, Color targetColor, Color replacementColor) throws AWTException
{
if(new Color(mapImg.getRGB(x, y)).equals(replacementColor))
{
return;
}
g.setColor(replacementColor);
g.fillRect(x, y, 1, 1);
if(new Color(mapImg.getRGB(x-1, y)).equals(targetColor))
{
FloodFill(x-1, y, targetColor, replacementColor);
}
if(new Color(mapImg.getRGB(x+1, y)).equals(targetColor))
{
FloodFill(x+1, y, targetColor, replacementColor);
}
if(new Color(mapImg.getRGB(x, y-1)).equals(targetColor))
{
FloodFill(x, y-1, targetColor, replacementColor);
}
if(new Color(mapImg.getRGB(x, y+1)).equals(targetColor))
{
FloodFill(x, y+1, targetColor, replacementColor);
}
}
I don't really know why you receive this error when you start from 310 or more, because the part of your code that deals with what you are referring to as "zip codes" is too bizarre to try and make sense of, and because in any case, making sense out of that would not benefit any other visitors of the site, only you.
What I suspect is happening is that by starting at 310 or above the arrangement of zip codes is such that your recursive flood-fill algorithm is required to do more painting than if you did not.
Which brings us to your recursive flood-fill algorithm.
That's not the right way to do flood-fill.
It might be considered right in the academia, but not in the real world.
If your algorithm is given a long stretch of pixels to paint, it will recurse for every single one of them. In your initialization code I see you setting the width of your frame to 1500, and elsewhere I see you using coordinates in excess of 3000, which means that you do in fact give your algorithm long stretches of pixels to paint. Which means that it recurses a lot. That's why you get a stack overflow exception.
To correct your problem, you need to rewrite your recursive flood-fill algorithm so that it does not recurse so much. For example, instead of recursing each time you visit a pixel to the left, have it loop to the left for as long as there are pixels to paint, and only recurse for the pixel above and the pixel below each painted pixel. The same holds for visiting pixels to the right. That's an easy way to reduce the recursion depth of your algorithm by orders of magnitude.
It also has the benefit of performing much better, because once you know all the pixels that you need to paint in a row, you can paint them all with a single drawing call, instead of performing one fillRect() per pixel. We are talking about orders of magnitude better performance here.
If that is not enough to solve your stack overflow problems, then you might want to consider replacing your algorithm with one which uses a stack data structure instead of actually invoking itself. Conversion of a recursive algorithm to a non-recursive that uses a stack data structure is something that you can look up and find plenty of solutions to.

Simple side-scrolling JFrame game lag, other questions haven't helped?

At the moment I'm trying to make a simple videogame in Java, just for fun. But there seems to be lag, and I'm not sure why it's happening. I'll give the lowdown:
The way it draws is using JFrame, and the actual drawing happens in the ImagePanel class. In ImagePanel, this is how I draw. It includes some things about debugging to show FPS and a timer to show length of run, but I'm not sure if that's important. It goes through multiple ArrayLists to show all the objects on the JFrame.
//Painting
public void paintComponent(Graphics g)
{
//Paint the background with its upper left corner at the upper left corner of the panel
g.drawImage(background, 0, 0, null);
//Paint each image in the foreground where it should go
for(MovingImage img : backgrounds)
{
g.drawImage(img.getImage(), (int)(img.getX()), (int)(img.getY()), null);
}
for(MovingImage img : foreground)
{
g.drawImage(img.getImage(), (int)(img.getX()), (int)(img.getY()), null);
}
if(g instanceof Graphics2D)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.scale(2, 2);
g2.setColor(Color.WHITE);
String milSpace;
if(timer%100 < 10)
milSpace = "0";
else
milSpace = "";
String secSpace;
if(timer/100 < 10)
secSpace = "0";
else
secSpace = "";
g2.drawString(secSpace + timer/100 + ":" + milSpace + timer%100, 10, 20);
//Debug
if(debug)
{
long currentTime = System.currentTimeMillis();
if (currentTime > nextSecond)
{
nextSecond += 1000;
frameInLastSecond = framesInCurrentSecond;
framesInCurrentSecond = 0;
}
framesInCurrentSecond++;
//g2.drawString("LagMS:" + (-frameRate - 10) + " FPS:" + frameInLastSecond, 20, 40); <-includes "lag"
g2.drawString("FPS:" + frameInLastSecond, 20, 40);
}
}
}
//Replaces the list of foreground images with the one given, and repaints the panel
public void updateImages(ArrayList<MovingImage> newForeground, ArrayList<MovingImage> newBackgrounds)
{
foreground = newForeground;
backgrounds = newBackgrounds;
//time checking
long time = System.currentTimeMillis();
lastTime = time;
repaint(); //This repaints stuff... you don't need to know how it works
}
Inside the primary class I made that includes a tick system, which causes it to be painted in the first place.
public void tick()
{
long lastTime = System.currentTimeMillis();
int place = 0;
boolean go = true;
while(go)
{
long time = System.currentTimeMillis(); //The current time
if(time - 10 > lastTime) //Update every .01 seconds, or 1 TICK (if time - 10 > lastTime)
{
lastTime = time;
//Reset the last time
place++;
imagePanel.incTime();
for(MovingImage object : movingObjects)
{
if(object instanceof Building)
{
object.incrementPosition(); //Augment position by velocity
if(place%500 == 0)//If 5 seconds have passed...
{
((Building) object).speedUp();//make it go a little faster!
}
}
if(object instanceof Player)
{
if(jumpKeyOn)
((Player) object).jump();//Initiate jump class assuming key is pressed
object.incrementPosition();
((Player) object).constrainPlayerToObjects(movingObjects, yMax);
if(object.getY()>yMax + 1000)
{
go = false;
}
}
}
//Repaint all the things in their new positions, possibly faster
for(MovingImage bg : backgrounds)
{
bg.incrementPosition();
if(place%500 == 0)
bg.setVelocity(bg.getXvel() - 0.1, 0);
}
/*
* Acceleration
*/
//Removes buildings once left screen
int i = 0;
while(i < movingObjects.size())
{
if(movingObjects.get(i) instanceof Building)
{
if(movingObjects.get(i).getX() + movingObjects.get(i).getImage().getWidth(null) < 0)
movingObjects.remove(i);
else
i++;
}
else
i++;
}
imagePanel.updateImages(movingObjects, backgrounds);
}
}
gameOver();
}
It's an endless loop that essentially runs the program. I used multiple ArrayLists in order to put different layers down. What am I doing that's causing it to lag? I'm still fairly new, but I'll answer any questions about the code or provide more details. I couldn't find any other questions that helped.
EDIT: There are some odd things I should mention. Occasionally it runs at nearly the full FPS, but most of the time not. I also noticed that when I ran another java program at the same time, it ran at nearly full speed.
EDIT 2: Should I include the entire primary class code and ImagePanel?

Categories