I have an issue with drawing potentially hundreds of thousands rectangles with Java2D.
At first what I did was something along these lines:
private void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
//Draw Graphics here
g.dispose();
bs.show();
}
I then realized after testing that this was horribly inefficient when having anything above 500 rectangles, so I thought that maybe drawing the graphics to a BufferedImage then displaying the BufferedImage would be more efficient, so I came up with this.
private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
private void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics g = image.createGraphics();
//Draw Graphics here
g.dispose();
Graphics g2 = bs.getDrawGraphics();
g2.drawImage(image, 0, 0, width, height, null);
g2.dispose();
bs.show();
}
However this was still just as inefficient as the first way, I'm having trouble thinking of a better way to do this, and I'd prefer to stay away from something like OpenGL for this.
At between 500-1000 rectangles it gets really slow, and anything above that the program freezes very quickly.
So it would seem collision detection seems to be the main problem, rather than Java2D rendering. This is how I've handled the detection, it checks if two rectangles are touching on any side. If someone has a better approach to this I would appreciate it, because I'm seeing how inefficient this is.
public boolean collides(Entity e) {
Point2D upperLeftIn = new Point2D.Double(bounds.getX() + 1, bounds.getY());
Point2D upperRightIn = new Point2D.Double(bounds.getX() + 8, bounds.getY());
Point2D lowerLeftIn = new Point2D.Double(bounds.getX() + 1, bounds.getY() + 9);
Point2D lowerRightIn = new Point2D.Double(bounds.getX() + 8, bounds.getY() + 9);
Point2D upperLeftDown = new Point2D.Double(bounds.getX(), bounds.getY() + 1);
Point2D lowerLeftUp = new Point2D.Double(bounds.getX(), bounds.getY() + 8);
Point2D upperRightDown = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + 1);
Point2D lowerRightUp = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + 8);
Line2D top = new Line2D.Double(upperLeftIn, upperRightIn);
Line2D bottom = new Line2D.Double(lowerLeftIn, lowerRightIn);
Line2D left = new Line2D.Double(upperLeftDown, lowerLeftUp);
Line2D right = new Line2D.Double(upperRightDown, lowerRightUp);
if (e.bounds.intersectsLine(top)) {
return true;
}
if (e.bounds.intersectsLine(bottom)) {
return true;
}
if (e.bounds.intersectsLine(left)) {
return true;
}
if (e.bounds.intersectsLine(right)) {
return true;
}
return false;
}
Although I'm not sure how to handle the fact that the contents/goal of the question changed completely due to the edit...
To summarize: As it was pointed out in the comment (and you seem to have anticipated), the rendering of even thousands of rectangles should not be so much an issue in Java2D. Internally, it is using hardware support with DirectX or OpenGL, and you really have to spill lots of antialiased text and complex, textured or gradient-painted shapes onto the screen in order to really slow it down.
That being said, it's really not unlikely that the collision detection is the bottleneck here.
Presumably, the method that you posted is in the Entity class. And presumably, e.bounds is a Rectangle2D. In this case, you could simply test for intersections with
public boolean collides(Entity e) {
return this.bounds.intersects(e.bounds);
}
It is not clear what you wanted to achieve with the creation of the Line2D objects there, and you should probably explain this in words. (Maybe some sort of "threshold" around the actual bounds?). But you should keep in mind that the intersectsLine method can be computationally expensive, at least compared to the test that you actually want to do. You should try to boil this down to interval checks.
But even if you do this sort of (micro?-)optimizations to your collides method, the problem may be a more general one: From what you have written so far, one has to assume that you are testing each entity for collisions with each other entity, and this method is called in a loop like
for (int i=0; i<allEntities.size(); i++)
{
for (int j=i+1; j<allEntities.size(); j++)
{
Entity ei = allEntities.get(i);
Entity ej = allEntities.get(j);
if (ei.collides(ej)) handleCollision();
}
}
If this is the case, no optimization of the implementation will help you, because the problem lies in the asymptotic complexity: The number of intersection tests (that is, the number of calls to the collides-method) grows quadratically with the number of objects. For 500 objects, you'll have to do do ~125.000 calls to the method. That's already quite a lot, and could hardly be done 60 times per second. But for 5000 objects, you don't need 1.250.000 calls, but 12.500.000 - this is 100 times as many, and of course, impossible to do at 60 FPS.
There are sophisticated data structures for the (pairwise) collision detection of so many objects. Unfortunately, "sophisticated" here often means "horribly complicated to implement". Bounding Volume Hierarchies may be one approach that can help to accelerate the collision detection with reasonable effort. But if you have "many" and "small" objects that are in a "small" space, Spatial Hashing may be the more appropriate solution. You can quickly find tutorials/blog entries and sample code using this keyword, one example is at Spatial hashing implementation for fast 2D collisions, but there are several others.
Related
I've tried:
1.Creating a separate variable called "factor" and multiplying or dividing literally everything with it: entity velocities, object sizes, fonts, resolution etc..
(the factor is always relative to the resolution so the objects are scaled properly)
public class Player extends Entity{
float size;
public Player(needed variables) {
super(needed variables);
resize();
}
public void resize() {
/*
Resize everything.
This method is supposed to be called from a separate resizing
function located in another class when the JFrame size is changed.
the function has to play with the choice between divide or multiply
variables with the factor
*/
}
public void tick() {
x += velX*factor;
y += velY*factor;
}
etc..
}
By using this factor to multiply literally everything, it makes the code really messy and hard to read sometimes.
2.Rendering to a BufferedImage and scaling the BufferedImage to fit to the JFrame.
void render() {
//Render the game to a new BufferedImage
BufferedImage renderedFrame = new BufferedImage(1920, 1080, BufferedImage.TYPE_RGB);
renderedFrame.createGraphics();
Graphics g = renderedFrame.getGraphics();
//Render the game ....
//Scale the BufferedImage to fit the current resolution and render it to the Canvas
BufferStrategy bs = getBufferStrategy();
Graphics f = bs.getDrawGraphics();
f.drawImage(renderedFrame.getScaledInstance(1280, 720, Image.SCALE_FAST), 0, 0, null);
f.dispose();
bs.show();
}
Which makes the code much more readable but then there comes 2 problems:
Mouse input problems and resizing the BufferedImage is taking too much resources which makes the game laggy.
3.I could basically try to make a separate unit system for the game.. but then there's the same problem, when it comes to rendering strings or rectangles I'd have to multiply everything with the factor and the code is horrible after that.
Is there any better ways of rendering 2D games? If no then I'll think about moving on to OpenGL.
Thanks in advance.
The way I've done this most successfully is by scaling the graphics object. You end up with something like the following:
final int gameUnitsPerScreenDim = 32;
void render(JPanel panel, Graphics2D g2) {
double pixelHeight = panel.getHeight();
double pixelsPerGameUnit = pixelHeight / gameUnitsPerScreenDim;
g2.scale(pixelsPerGameUnit, pixelsPerGameUnit);
...
}
And then for the simulation, you use game units. How big a game unit actually is is a bit arbitrary, although if you're making a tiled game there's probably some obvious value that it should be.
Instead of using scale, you can also create an AffineTransform which lets you reuse it:
if (this.tf == null || /* image size changed since the last frame */) {
...
this.tf = AffineTransform.getScaleInstance(pxPerGu, pxPerGu);
}
g2.setTransform(this.tf);
(Calling scale creates a new AffineTransform every time you call it.)
That's even a little more efficient, although probably not by much.
(If you want, you can also use a transform to invert the y-axis and translate so the origin is at the center of the image. This makes a lot of trigonometry and stuff feel more natural. Inverting the y-axis makes working with text a pain, though.)
Also, using OpenGL is probably better. Having written a couple of simple games using Swing for fun, I don't see a good reason to do it.
I am trying to draw an Oval with a for loop iterating through a list of coordinates that each contain an x value and y value. Currently, it does not seem to be drawing anything after I start the program. It draws the first time, but when I try drawing when the program is running, it doesn't seem to be drawing.
Here is the code for drawing:
private void render(){
bs = display.getCanvas().getBufferStrategy();
if(bs == null){
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Draw Here!
DrawGrid(g);
g.fillOval(100, 100, 10, 10);//this seems to draw
for(int i = 0; i < points.size();i++){//this doesn't draw....
System.out.println(points.get(i));
g.drawString(points.get(i).toString(), points.get(i).x*100-5+100, points.get(i).y-5-300);
g.fillOval(points.get(i).x*100-5+100, points.get(i).y-5-300, 10, 10);
}
//End Drawing!
bs.show();
g.dispose();
}
If you need more details, I am using graphics from java.awt library. Also, I have done this in the past, but I don't know why it isn't working this time.
This part is your issue:
points.get(i).x*100-5+100
Specifically x*100
You are drawing off screen. We can see this by breaking it down:
Lets assume that you have a point of x=28.
Lets do the math on that:
For X = 28 you will have the following calc: (28 * 100) - (5 + 100) = 2695
That X point of 2695 looks very large to me. You would need a 4k screen or ultrawide screen to see it.
The solution:
Have a think about why you are using x*100, and reduce it so that the point fits on your screen. Also, if you have a small y point, then it will be in the negatives (Example: 15-5-300 = -290), and will probably draw above your screen and out of sight.
I'm working on creating a voxel engine in LWJGL 3, I have all the basics down (chunks, mesh rendering, etc).
Now I'm working on adding physics using JBullet. This is my first time using JBullet directly, but I've used Bullet before in other 3D engines.
From here I gathered that all I needed to do to create a collision object the same shape as my mesh was the plug the vertices and indices into a TriangleIndexVertexArray and use that for a BvhTriangleMeshShape.
Here is my code:
float[] coords = mesh.getVertices();
int[] indices = mesh.getIndices();
if (indices.length > 0) {
IndexedMesh indexedMesh = new IndexedMesh();
indexedMesh.numTriangles = indices.length / 3;
indexedMesh.triangleIndexBase = ByteBuffer.allocateDirect(indices.length*Float.BYTES).order(ByteOrder.nativeOrder());
indexedMesh.triangleIndexBase.asIntBuffer().put(indices);
indexedMesh.triangleIndexStride = 3 * Float.BYTES;
indexedMesh.numVertices = coords.length / 3;
indexedMesh.vertexBase = ByteBuffer.allocateDirect(coords.length*Float.BYTES).order(ByteOrder.nativeOrder());
indexedMesh.vertexBase.asFloatBuffer().put(coords);
indexedMesh.vertexStride = 3 * Float.BYTES;
TriangleIndexVertexArray vertArray = new TriangleIndexVertexArray();
vertArray.addIndexedMesh(indexedMesh);
boolean useQuantizedAabbCompression = false;
BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(vertArray, useQuantizedAabbCompression);
CollisionShape collisionShape = meshShape;
CollisionObject colObject = new CollisionObject();
colObject.setCollisionShape(collisionShape);
colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 1f)));
dynamicsWorld.addCollisionObject(colObject);
} else {
System.err.println("Failed to extract geometry from model. ");
}
I know that the vertices and indices are valid as I'm getting them here after drawing my mesh.
This seems to somewhat work, but when I try to drop a cube rigidbody onto the terrain, it seems to collide way above the terrain! (I know that the cube is setup correctly because if I remove the mesh collider it hits the base ground plane at y=0).
I thought maybe it was a scaling issue (although I don't see how that could be), so I tried changing:
colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 1f))); to:
colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 0.5f)));
But after changing the scale from 1 it acted like the mesh collider didn't exist.
It's hard to find any resources or code for JBullet surrounding mesh collision, and I've been working on this for almost 2 days, so I'm hoping maybe some of you people who have done it before can help me out :)
Update 1:
I created an implementation of the IDebugDrawer so I could draw the debug infomation in the scene.
To test it I ran it with just a basic ground plane and a falling cube. I noticed that when the cube is falling the aabb matches the cube size, but when it hits the floor the aabb becomes significantly larger then it was.
I'm going to assue that this is normal Bullet behavior due to collition bouncing, and look at that later as it doesn't effect my current problem.
I re-enabled the generation of the colliders from the chunk meshs, and saw this:
It appears that the aabb visualization of the chunk is a lot higher then the actual chunk (I know my y positioning of the overall collision object is correct).
I'm going to try to figure out if I can draw the actual collision mesh or not.
Update 2:
As far as I can see looking at the source, the meshof the colliders should be drawing in debug, so I'm not sure why it isn't.
I tried changing the Box rigidbody to a sphere, and it actually rolled across the top of the visualized aabb for the terrain collider. It just rolled flat though and didn't go hit or down where there where hills or dips in the terrain where, so it was obviously just rolling across the flat top of the aabb.
So after adding in the Debug Drawer, I was confused as to why the aabb was x2 larger then it should have been.
After spending hours trying little adjustments, I noticed something odd - there was a 0.25 gap between the collider and the edge of the chunk. I proceeded to zoom out and surprisingly noticed this:
There is an extera row and column of colliders? No that doesn't make sense, there should be 5x5 colliders to match the 5x5 chunks.
Then I counted blocks and realized that the colliders where spanning 64 blocks (my chunks are 32x32!).
I quickly realized that this was a scaling issue, and after adding
BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(vertArray, useQuantizedAabbCompression);
meshShape.setLocalScaling(new Vector3f(0.5f, 0.5f, 0.5f));
To scale the colliders down by half, everything fit and worked! My "sphere" rolled and came to a stop where there was a hill in the terrain like it should.
My full code for coverting an LWJGL mesh to a JBullet mesh collder is:
public void addMesh(org.joml.Vector3f position, Mesh mesh){
float[] coords = mesh.getVertices();
int[] indices = mesh.getIndices();
if (indices.length > 0) {
IndexedMesh indexedMesh = new IndexedMesh();
indexedMesh.numTriangles = indices.length / 3;
indexedMesh.triangleIndexBase = ByteBuffer.allocateDirect(indices.length*Integer.BYTES).order(ByteOrder.nativeOrder());
indexedMesh.triangleIndexBase.rewind();
indexedMesh.triangleIndexBase.asIntBuffer().put(indices);
indexedMesh.triangleIndexStride = 3 * Integer.BYTES;
indexedMesh.numVertices = coords.length / 3;
indexedMesh.vertexBase = ByteBuffer.allocateDirect(coords.length*Float.BYTES).order(ByteOrder.nativeOrder());
indexedMesh.vertexBase.rewind();
indexedMesh.vertexBase.asFloatBuffer().put(coords);
indexedMesh.vertexStride = 3 * Float.BYTES;
TriangleIndexVertexArray vertArray = new TriangleIndexVertexArray();
vertArray.addIndexedMesh(indexedMesh);
boolean useQuantizedAabbCompression = false;
BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(vertArray, useQuantizedAabbCompression);
meshShape.setLocalScaling(new Vector3f(0.5f, 0.5f, 0.5f));
CollisionShape collisionShape = meshShape;
CollisionObject colObject = new CollisionObject();
colObject.setCollisionShape(collisionShape);
colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 1f)));
dynamicsWorld.addCollisionObject(colObject);
} else {
System.err.println("Failed to extract geometry from model. ");
}
}
Update 1:
Even though the scaling was the fix for said prolem, it caused me to look deeper and realize that I mistakenly was using to block size (0.5f) for the mesh scaling factor in my mesh view matrix. Changing the scale to 1 like it should be fixed it.
I am trying to animate a propeller in java and have come to this code:
int x = 0;
int y = 230;
int h = 40;
int i = 0;
int center = 250;
void setup() {
size(500, 400);
}
void draw () {
if (i == 0) {
if(y>200) {
ellipse(x, y, 20, h);
y--;
h+=2;
x+=1;
} else { i = i + 1; }
}
if (i == 1) {
if(y<=230) {
ellipse(x, y, 20, h);
y++;
h-=2;
x+=1;
} else { i = i + 1; }
}
if (i == 2) {
if(h<70) {
ellipse(x, y, 20, h);
y++;
h+=1;
x+=1;
} else { i = i + 1; }
}
if (i == 3) {
if(h>=40) {
ellipse(x, y, 20, h);
y--;
h-=1;
x+=1;
} else { i = 0; }
}
}
Is there a way of making this shorter, because I want to have 4 propellers and dont want to have so much code for this part.
You're going about this the wrong way. Very wrong, in fact. And the reason might be that you think you're doing this in "Java...in the program 'processing'"
The reason this sort of thinking is wrong is because it is equivalent to someone working with C++ saying, "I am creating classes in C using the program 'C++'". Processing is based on Java but it is a different programming language. If you're uncomfortable stretching the idea of Processing to that of a "programming language" then at least think of it as a framework...a framework that provides you with its own implementation of various tools that are put together to help with the creation of art using computers.
Now your thinking seems to have affected you in your particular case a lot. Just like Jose Gonzalez above suggested, you haven't even thought about rotate(), which is a function built into Processing. Tim B's suggestion about Objects is spot on as well but if you want to do things in Processing, try to do them the Processing way.
Here's a very quick sketch that I did for you to understand what I mean:
Prop myProp1;
Prop myProp2;
Prop myProp3;
Prop myProp4;
float angle;
void setup() {
size(500, 500);
background(255);
angle = 0;
fill(0);
ellipseMode(CENTER);
myProp1 = new Prop(50,50);
myProp2 = new Prop(75,75);
myProp3 = new Prop(100,100);
myProp4 = new Prop(125,125);
}
void draw() {
background(255);
angle = ((angle + 0.1) % 360);
myProp1.buildAndRotate(angle, width*3/4, height/4);
myProp2.buildAndRotate(angle, width/4, height/4);
myProp3.buildAndRotate(angle, width*3/4, height*3/4);
myProp4.buildAndRotate(angle, width/4, height*3/4);
}
class Prop {
int propX;
int propY;
int propW;
int propH;
Prop() {
propX = 0;
propY = 0;
propW = 50;
propH = 50;
}
Prop(int w, int h) {
propX = 0;
propY = 0;
propW = w;
propH = h;
}
void buildAndRotate(float angle, float moveToX, float moveToY) {
pushMatrix();
translate(moveToX, moveToY);
rotate(angle);
ellipse(propX, propY, propW, propH);
ellipse(propX+propW/2, propY+propH/2, propW, propH);
ellipse(propX-propW/2, propY+propH/2, propW, propH);
ellipse(propX+propW/2, propY-propH/2, propW, propH);
ellipse(propX-propW/2, propY-propH/2, propW, propH);
popMatrix();
}
}
Now, this is by no means meant to be the way to do things in Processing but it uses various tools provided by the programming language to do exactly what you want to do. Also, the Object Prop can be built in various different ways and my implementation is not supposed to be top-notch at all.
So what is going on? Well, run the sketch and you will see four propellers rotating on their own axes. I just put four of them there, you can delete them or add more as you please.
What the hell is a Prop made of? It is made of five ellipses. The concept, and this may be different than yours, is based on the idea of a ceiling fan (or other fans for that matter or even a prop engine). These fans have a circular thing in the middle to which all the blades are attached. The center circle rotates and that results in all the blades around it rotating with it. What you get in the end is a rotating fan.
How is this thing rotating? Similar to the fan analogy above, there is an ellipse in the middle that rotates around its center. There are four ellipses "attached" to it that rotate around this center ellipses. This gives the illusion of a prop rotating. What you have to understand is that in Processing rotate() rotates things around the origin. This is the reason the center ellipse starts at (0,0). Then later in buildAndRotate() it is translated using translate(). Using translate() does not move the object, it instead moves the origin resulting in the object moving with it, and then when you execute rotate() it rotates the object around its center. Since all the other ellipses are built around this center ellipse, they all rotate around it too (in actual implementation, they're just rotating around the origin, you can see that by removing the center ellipse). pushMatrix() and popMatrix() are used so all the translate() and rotate() commands don't affect everything else in the sketch and keep all movements and rotations applied to each object to that very object.
What else is going on? The rest is pretty simple (this is all very simple once you get the hang of it). Background is being cleared constantly which is a common animation technique to give the illusion of movement. You can delete the statement for background() from draw() but I wouldn't recommend it because it will leave a big black round circle after a while and you won't be able to see the props. The angle is being changed constantly in the draw() method which is why you see the props rotating. You can speed up or slow down the rotation by changing that + 0.1 value being added to angle if you want.
What else do I do? Read the reference (not the Java reference, Processing has its own reference) by using Google or following links such as: http://www.processing.org/reference/rotate_.html, http://www.processing.org/reference/pushMatrix_.html, http://www.processing.org/reference/popMatrix_.html, http://www.processing.org/reference/translate_.html, and many more.
Hope this helps and leave questions in the comment if you need clarification on something.
You need to think in terms of objects and iteration rather than writing out everything explicitly. You are correct that the code you have above contains a lot of un-needed duplication, which is a bad thing.
For a more complex case you would define each part of the propeller as an object. Have an array of parts within the Propeller object. Each time to do the draw you run through the list of objects and render each one out.
In this case it can be even simpler, just use a for loop.
At the top of your program define:
private static final int NUM_BLADES = 4;
Then you want a loop that looks something like this:
for (int i=0;i<360;i+=360/NUM_BLADES) {
// Draw elipse at current rotation position + the angle for this blade
}
Now you can change the number of blades just by changing the static define as well.
My assignment is to implement an algorithm to color a closed shape starting from a given (x,y) coordinate and "spread" via recursive calls untill it reaches the borders of the shape. So far this is what I've come up with:
private void color(int x, int y) {
g2d.draw(new Line2D.Double(x, y, x, y));
if (!robot.getPixelColor(x - 1, y).equals(Color.BLACK) &&
!robot.getPixelColor(x - 1, y).equals(Color.RED)) {
color(x - 1, y);
} else if (!robot.getPixelColor(x + 1, y).equals(Color.BLACK) &&
!robot.getPixelColor(x - 1, y).equals(Color.RED)) {
color(x + 1, y);
} else if (!robot.getPixelColor(x, y - 1).equals(Color.BLACK) &&
!robot.getPixelColor(x - 1, y).equals(Color.RED)) {
color(x, y - 1);
} else if (!robot.getPixelColor(x, y + 1).equals(Color.BLACK) &&
!robot.getPixelColor(x - 1, y).equals(Color.RED)) {
color(x, y + 1);
}
}
The Robot class' getPixelColor is the only way I found to get the color of a given pixel (as far as I know another would be getRGB, but that only works on Image objects). To my understanding this should work, as the outer lines of the shape are definitely black, and the initial x and y values come from a MouseListener, so they are inside the shape, however I get the following error:
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
at sun.java2d.pipe.BufferedContext.validateContext(BufferedContext.java:110)
at sun.java2d.d3d.D3DRenderer.validateContextAA(D3DRenderer.java:42)
at sun.java2d.pipe.BufferedRenderPipe$AAParallelogramPipe.fillParallelogram(BufferedRenderPipe.java:445)
at sun.java2d.pipe.PixelToParallelogramConverter.drawGeneralLine(PixelToParallelogramConverter.java:264)
at sun.java2d.pipe.PixelToParallelogramConverter.draw(PixelToParallelogramConverter.java:121)
at sun.java2d.SunGraphics2D.draw(SunGraphics2D.java:2336)
at dline.DrawingSpace.color(DrawingSpace.java:87)
at dline.DrawingSpace.color(DrawingSpace.java:93)
at dline.DrawingSpace.color(DrawingSpace.java:90)
at dline.DrawingSpace.color(DrawingSpace.java:93)
at dline.DrawingSpace.color(DrawingSpace.java:90)
(drawingSpace is a sub-class of JPanel)
The teacher did tell us that this is memory consuming, however it's supposed to be a working algorithm, so I'm doing something wrong, obviously. Any help would be much appriciated, thank you.
You can try to increase the Stack size: How to increase the Java stack size?
Probably you have a bug in your algorithm, or the shape is too big. What helps if you 'draw' your algorithm on a piece of graph paper. That way you can check your algorithm.
I'm guessing that you're backtracking onto previously visited pixels. The pixel you just drew probably won't be visible to robot until after you return from color, so it will not appear red from the previous painting.
Do you have a reference to the java.awt.Shape? A much simpler way than using the robot would be to use Shape.contains(Point) to see whether it's in the shape you're supposed to draw.
The basic algorithm either way is depth-first traveral. To do a DFS when there are possible cycles, you can record the pixels you've already drawn.
//java.awt.Point
Set<Point> paintedPixels = new HashSet<Point>();
private void color(int x, int y) {
if ( paintedPixels.contains(new Point(x, y)) ) {
//already painted
return;
}
paintedPixels.add(new Point(x, y));
//...
}
Now, this could still result in a very deep search. You might consider instead using a non-recursive breadth-first traveral. See the Wikipedia article on Flood Fill.
The problem with implementing this as a recursive algorithm is that it has (for bigger images) a very high recursion depth.
In Java (and most other imperative programming languages, too) the maximal recursion depth is limited by the amount of stack space for each thread, since it must keep a stack frame for each method invocation there.
You may try smaller images first, and try to increase the stack size with the -xss parameter.
Edit: As pointed out by Mark, the Robot will not get any pixels until your drawing is complete, since often your drawing is double-buffered (i.e. the Swing engine lets you paint first on an image, and draws then the complete image to the screen).
Also, you are not converting between device (screen) and user (component) coordinates for the lookup.
You wrote:
The Robot class' getPixelColor is the only way I found to get the color of a given pixel (as far as I know another would be getRGB, but that only works on Image objects).
So, why don't you use an Image object? Fill your shape while drawing on the Image, and then draw the whole image at once to the screen.
And your method can be made much more readable if you transfer the "is already painted" test inside the recursive call:
private void color(int x, int y) {
// getPixel invokes something in the image - or replace it here.
Color org = getPixel(x,y);
if (org.equals(Color.BLACK)) {
// reached the border
return;
}
if (org.equals(Color.RED)) {
// already painted before
return;
}
g2d.draw(new Line2D.Double(x, y, x, y));
color(x-1, y);
color(x+1, y);
color(x, y-1);
color(x, y-1);
}