I want to draw an array of X and Y integers to a panel in a Java frame.
What is the best way to draw the line (currently I'm using Graphic's drawPolyline)?
How can I efficiently scale the integer values so they all fit in the panel area without knowing the max (Y) value?
Update, for example
public void paint(Graphics g)
{
int height = panel.getHeight();
int width = panel.getWidth();
int[] xPoints = { ... values ... };
int[] yPoints = { ... values ... };
int nPoints = dataLength;
// Scale xPoints and yPoints so they fit in the area of width and height
// and draw line
g.drawPolyline(xPoints, yPoints, nPoints);
g.dispose();
}
Without knowing both the x and y maximum values, I don't think this can be done, since you can't then calculate the scale needed. But if you have an array of points, then you can certainly search it to find the minimum and maximum x and y values.
If you have a means of getting the maximum values, read on (and for the benefit of others with a similar problem).
Find the maximum difference between any two x values between any two y values; call them max(dx) and max(dy) - these are max(x)-min(x) and max(y)-min(y) respectively.
Take the greater of width/max(dx) and height/max(dy). That number provides your scale; just modify every x value using ((x-min(x))/scale) and each y value using ((y-min(y))/scale)
You should now have the largest shape that will fit oriented relative 0,0.
Note: I have have negative coordinates, you will have to adjust them into the positive coordinate range before applying these formulas.
Related
To avoid wasting your time I will make it short:
My project: a graphical presentation of the Mandelbrot set.
My progress: finished except a bug in the zoom in/zoom out feature
The bug: The zoom in scale is smaller (slower) than the zoom out scale
The shorter version: how can I simulate a zoom in/out by a factor on a jpanel.
In the end a small section of the jPanel should be enlarged to the whole jPanel.
Every pixel in the jPanel represents an integer number, but you don't have to worry about a lack of integers after the zoom in.
The longer version (only if you need more information):
the zoom-function in the Mandelbrot set:
public void recalculate(int x, int y, int width, int height, double scale){
downright=new Complex(upleft.getReal()+stepwidth*(x+width/scale),upleft.getImg()-stepwidth*(y+height/scale));
upleft=new Complex(upleft.getReal()+stepwidth*(x-width/scale), upleft.getImg()-stepwidth*(y-height/scale));
stepwidth=(downright.getReal()-upleft.getReal())/width;
calc(lastrep);
}
A short terminology:
downright is the bottom-right complex number, the last pixel to be painted on the jpanel
upleft is, therefore, the top-left complex-number, the first pixel to be painted on the jpanel
stepwidth is the distance between each complex number represented by one pixel
calc is finally the function that calculates every color for each pixel according to the Mandelbrot set rules
the width and height parameters are the pixel width/height of the jpanel, x and y are the coordination where to zoom in and scale the factor for the zoom.
When I call this function mandelbrot.recalculate(x,y, getWidth(), getHeight(), 10); than this should zoom in on the point (x,y) so that the new represented image is 1/5 of the actual image.
1/5 should be because the start of the image (upleft) is 1/10 of the amount of pixels of the full width and height shifted to the up and left of the point (x,y) and the end of the image (downright) is 1/10 of the amount of pixels of the full width and height shifted down and right.
So if I call the function after the zoom in like this: mandelbrot.recalculate(x,y, getWidth(), getHeight(), 0.1);
it should reverse the whole process, but it doesn't
this is the intended zoom:
The Solution was rather simple.
The Problem was the attempt to calculate the zoom in as well as the shift to a new midpoint in one go.
Since the shift to the new midpoint is dependent on the zoom in, you can't put that into one equation, or at least only with a lot of effort.
I changed my code like shown below, so that I first calculate the zoom in and then shift the anchor to the wanted position. Sometimes the longer way is the better way.
//The new midpoint needs the old stepwith to calculate correctly
Complex newmidpoint = new Complex(upleft.getReal()+x*stepwidth, upleft.getImg()-y*stepwidth);
//zooming in
double distwidth = downright.getReal()-upleft.getReal();
double distheight = upleft.getImg()-downright.getImg();
double newreal = upleft.getReal()+(1/scale)*distwidth;
double newimg = upleft.getImg()-(1/scale)*distheight;
downright=new Complex(newreal, newimg);
stepwidth=(downright.getReal()-upleft.getReal())/width;
//the old midpoint is actually the already zoomed in midpoint, it needs the new stepwidth
Complex oldmidpoint = new Complex(upleft.getReal()+width/2.0*stepwidth,upleft.getImg()-height/2.0*stepwidth);
Complex diffmidpoint = newmidpoint.subtract(oldmidpoint);
upleft=new Complex(upleft.getReal()+diffmidpoint.getReal(),upleft.getImg()+diffmidpoint.getImg());
downright=new Complex(downright.getReal()+diffmidpoint.getReal(), downright.getImg()+diffmidpoint.getImg());
calc(lastrep);
I have made a simple game and I have a simple way to detect when I have collected a coin but it is very hard to match its position exactly.
public class Token {
private String name;
int x;
int y;
private BufferedImage image;
public Token (String nameIn, int xIn, int yIn, BufferedImage imageIn)
{
name = nameIn;
x = xIn;
y = yIn;
image = imageIn;
}
public boolean collected(Hero frylark) {
if (frylark.getX() == x && frylark.getY() == y) {
return true;
}
else {
return false;
}
}
}
Is there any way i can have a buffer of say 10 pixels instead of
matching the position of the coin exactly.
A distance between two points in a two-dimensional field is the sum of the squares of the differences between their corresponding coordinates:
public boolean collected(Hero frylark) {
return Math.sqrt(Math.pow(frylark.getX() - x , 2) +
Math.pow(frylark.getY() - y , 2)
) <= 10.0;
}
Based on Mureinik's answer, you can do this faster by not use Math.pow nor Math.sqrt.
double dx = frylark.getX() - x;
double dy = frylark.getY() - y;
return dx*dx + dy*dy <= 10.0*10.0;
I have made a simple game and I have a simple way to detect when I have collected a coin but it is very hard to match its position exactly.
I will propose a slightly different approach for you. If you attempt to detect collision by using only the x and y coordinates, it is very hard to detect collision since you need both pixels to hit at the same spot.
This problem arises especially when you try to check collision for images of different sizes:
Exmaple:
With your current implementation, in order for the Game Character to hit the coin, the red pixel (top left hand corner) has to collide, and you end up needed to add a buffer for images of different sizes to check for collision.
I will advise returning a bounding box for each object and check weather their bounding boxes intersects:
public boolean collected(Hero h){
Rectangle heroBox = new Rectangle (h.getX(), h.getY(), h.getWidth(), h.getHeight());
Rectangle coinBox = new Rectangle (x, y, width, height);
return(coinBox.intersects(heroBox));
}
You will need the width and height (which is usually the width and height of your images) of your objects for creating the bounding box.
Advantage:
You no longer have to check the size of each image and set the buffer for them individually.
Is there any way i can have a buffer of say 10 pixels instead of
matching the position of the coin exactly.
Adding a buffer:
If you still want a buffer, say 10 pixel. We can still apply it in this implementation:
public boolean collected(Hero h, int buffer){
Rectangle heroBox = new Rectangle (h.getX(), h.getY(), h.getWidth() + buffer, h.getHeight() + buffer);
Rectangle coinBox = new Rectangle (x, y, width + buffer , height + buffer);
return(coinBox.intersects(heroBox));
}
By adding the given buffer, we enlarge the area of the bounding boxes, hence making it more sensitive. You can always tweak from my example to add the buffer on one of the objects, both objects, or on only the width or the height of either objects.
Despite passing equal (exactly equal) coordinates for 'adjacent' edges, I'm ending up with some strange lines between adjacent elements when scaling my grid of rendered tiles.
My tile grid rendering algorithm accepts scaled tiles, so that I can adjust the grid's visual size to match a chosen window size of the same aspect ratio, among other reasons. It seems to work correctly when scaled to exact integers, and a few non-integer values, but I get some inconsistent results for the others.
Some Screenshots:
The blue lines are the clear color showing through. The chosen texture has no transparent gaps in the tilesheet, as unused tiles are magenta and actual transparency is handled by the alpha layer. The neighboring tiles in the sheet have full opacity. Scaling is achieved by setting the scale to a normalized value obtained through a gamepad trigger between 1f and 2f, so I don't know what actual scale was applied when the shot was taken, with the exception of the max/min.
Attribute updates and entity drawing are synchronized between threads, so none of the values could have been applied mid-draw. This isn't transferred well through screenshots, but the lines don't flicker when the scale is sustained at that point, so it logically shouldn't be an issue with drawing between scale assignment (and thread locks prevent this).
Scaled to 1x:
Scaled to A, 1x < Ax < Bx :
Scaled to B, Ax < Bx < Cx :
Scaled to C, Bx < Cx < 2x :
Scaled to 2x:
Projection setup function
For setting up orthographic projection (changes only on screen size changes):
.......
float nw, nh;
nh = Display.getHeight();
nw = Display.getWidth();
GL11.glOrtho(0, nw, nh, 0, 1, -1);
orthocenter.setX(nw/2); //this is a Vector2, floats for X and Y, direct assignment.
orthocenter.setY(nh/2);
.......
For the purposes of the screenshot, nw is 512, nh is 384 (implicitly casted from int). These never change throughout the example above.
General GL drawing code
After cutting irrelevant attributes that didn't fix the problem when cut:
#Override
public void draw(float xOffset, float yOffset, float width, float height,
int glTex, float texX, float texY, float texWidth, float texHeight) {
GL11.glLoadIdentity();
GL11.glTranslatef(0.375f, 0.375f, 0f); //This is supposed to fix subpixel issues, but makes no difference here
GL11.glTranslatef(xOffset, yOffset, 0f);
if(glTex != lastTexture){
GL11.glBindTexture(GL11.GL_TEXTURE_2D, glTex);
lastTexture = glTex;
}
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2f(texX,texY + texHeight);
GL11.glVertex2f(-height/2, -width/2);
GL11.glTexCoord2f(texX + texWidth,texY + texHeight);
GL11.glVertex2f(-height/2, width/2);
GL11.glTexCoord2f(texX + texWidth,texY);
GL11.glVertex2f(height/2, width/2);
GL11.glTexCoord2f(texX,texY);
GL11.glVertex2f(height/2, -width/2);
GL11.glEnd();
}
Grid drawing code (dropping the same parameters dropped from 'draw'):
//Externally there is tilesize, which contains tile pixel size, in this case 32x32
public void draw(Engine engine, Vector2 offset, Vector2 scale){
int xp, yp; //x and y position of individual tiles
for(int c = 0; c<width; c++){ //c as in column
xp = (int) (c*tilesize.a*scale.getX()); //set distance from chunk x to column x
for(int r = 0; r<height; r++){ //r as in row
if(tiles[r*width+c] <0) continue; //skip empty tiles ('air')
yp = (int) (r*tilesize.b*scale.getY()); //set distance from chunk y to column y
tileset.getFrame(tiles[r*width+c]).draw( //pull 'tile' frame from set, render.
engine, //drawing context
new Vector2(offset.getX() + xp, offset.getY() + yp), //location of tile
scale //scale of tiles
);
}
}
}
Between the tiles and the platform specific code, vectors' components are retrieved and passed along to the general drawing code as pasted earlier.
My analysis
Mathematically, each position is an exact multiple of the scale*tilesize in either the x or y direction, or both, which is then added to the offset of the grid's location. It is then passed as an offset to the drawing code, which translates that offset with glTranslatef, then draws a tile centered at that location through halving the dimensions then drawing each plus-minus pair.
This should mean that when tile 1 is drawn at, say, origin, it has an offset of 0. Opengl then is instructed to draw a quad, with the left edge at -halfwidth, right edge at +halfwidth, top edge at -halfheight, and bottom edge at +halfheight. It then is told to draw the neighbor, tile 2, with an offset of one width, so it translates from 0 to that width, then draws left edge at -halfwidth, which should coordinate-wise be exactly the same as tile1's right edge. By itself, this should work, and it does. When considering a constant scale, it breaks somehow.
When a scale is applied, it is a constant multiple across all width/height values, and mathematically shouldn't make anything change. However, it does make a difference, for what I think could be one of two reasons:
OpenGL is having issues with subpixel filling, ie filling left of a vertex doesn't fill the vertex's containing pixel space, and filling right of that same vertex also doesn't fill the vertex's containing pixel space.
I'm running into float accuracy problems, where somehow X+width/2 does not equal X+width - width/2 where width = tilewidth*scale, tilewidth is an integer, and X is a float.
I'm not really sure about how to tell which one is the problem, or how to remedy it other than to simply avoid non-integer scale values, which I'd like to be able to support. The only clue I think might apply to finding the solution is how the pattern of line gaps isn't really consistant (see how it skips tiles in some cases, only has vertical or horizontal but not both, etc). However, I don't know what this implies.
This looks like it's probably a floating point precision issue. The critical statement in your question is this:
Mathematically, each position is an exact multiple [..]
While that's mathematically true, you're dealing with limited floating point precision. Sequences of operations that should mathematically produce the same result can (and often do) produce slightly different results due to rounding errors during expression evaluation.
Specifically in your case, it looks like you're relying on identities of this form:
i * width + width/2 == (i + 1) * width - width/2
This is mathematically correct, but you can't expect to get exactly the same numbers when evaluating the values with limited floating point precision. Depending on how the small errors end up getting rounded to pixels, it can result in visual artifacts.
The only good way to avoid this is that you actually use the same values for coordinates that must be the same, instead of using calculations that mathematically produce the same results.
In the case of coordinates on a grid, you could calculate the coordinates for each grid line (tile boundary) once, and then use those values for all draw operations. Say if you have n tiles in the x-direction, you calculate all the x-values as:
x[i] = i * width;
and then when drawing tile i, use x[i] and x[i + 1] as the left and right x-coordinates.
I have a system that generates chunks of 2d game map tiles. Chunks are 16x16 tiles, tiles are 25x25.
The chunks are given their own coordinates, like 0,0, 0,1, etc. The tiles determine their coordinates in the world based on which chunk they're in. I've verified that the chunks/tiles are all showing the proper x/y coordinates.
My problem is translating those into screen coordinates. In a previous question someone recommended using:
(worldX * tileWidth) % viewport_width
Each tile's x/y are run through this calculation and a screen x/y coordinate is returned.
This works for tiles that fit within the viewport, but it resets the screen x/y position calculation for anything off-screen.
In my map, I load chunks of tiles within a radius around the player so some of the inner tiles will be off-screen (until they move around, tile positions on the screen are moved).
I tried a test with a tile that would be off screen:
Tile's x coord: 41
41 * 25 = 1025
Game window: 1024
1025 % 1024 = 1
This means that tile x (which, if the screen 0,0 is at map 0,0, should be at x:1025, just off the right-hand side of the screen) is actually at x:1, appearing in the top-left.
I can't think of how to properly handle this - it seems to me like I need take the tileX * tileWidth to determine it's "initial screen position" and then somehow use an offset to determine how to make it appear on screen. But what offset?
Update: I already store an x/y offset value when the player moves, so I know how to move the map. I can use these values as the current offset, and if someone saves the game I can simply store those and re-use them. There's no equation necessary, I would just have to store the cumulative offsets.
The modulo (worldX*tileWidth % screenWidth) is what's causing it to reset. Modulo (%) gives you the remainder of an integer division operation; so, if worldX * tileWidth is greater than screenWidth, it will give you the remainder of (worldX * tileWidth) / screenWidth; if worldX * tileWidth is screenWidth+1, remainder is 1: it starts over at the beginning of the row.
If you eliminate the modulo, it will continue to draw tiles past the edge of the screen. If your drawing buffer is the same size as the screen, you'll need to add a check for tiles at the edge of the screen to make sure you only draw the tile portion that will be visible.
If you're trying to keep the player centered on the screen, you need to offset each tile by the player's offset from tile 0,0 in pixels, minus half the screen width:
offsetX = (playerWorldX * tileWidth) - (screenWidth / 2);
screenX = (worldX * tileWidth) - offsetX;
x = ((worldX*tileWidth) > screenWidth) ? worldX*tileWidth : (worldX*tileWidth)%screenWidth;
That should work. Though I recommend implementing something like an interface and letting each tile decide where they want to be rendered. Something like this
interface Renderable {
void Render(Graphics2D g)
..
}
class Tile implements Renderable{
int x,y
//other stuff
Render(Graphics2D g){
if (!inScreen()){
return;
}
//...
//render
}
boolean inScreen(){
//if the map moves with the player you need to define the boundaries of your current screenblock in terms of the global map coordinates
//What you can do is store this globally in a singleton somewhere or pass it to the constructor of each tile.
//currentBlock.x is then player.x - screenWidth/2
//currentBlock.width is then player.x + screenWidth/2;
//similar for y
if(this.x < currentBlock.x || this.x > currentBlock.Width)
return false;
if (this.y < currentBlock.y || this.y > currentBlock.height)
return false;
return true;
//If the map are in blocks (think zelda on snes where you go from one screenblock to another) you still need to define the boundaries
//currentBlock.x = (player.x / screenWidth) (integer division) *screenWidth;
//currentBlock.width = (player.x /screenWidth) (...) * screenWidth + screenWidth;
//same for y
//Then perform above tests
}
I seem to be having a bit of a math fail here... I need to convert an (x,y) point from one coordinate space to another - not in the sense of polar to cartesian or anything of the sort... just from one bound to another. i.e., for a particular (x,y) that falls in the rectangle with lower left (-100, -100) and upper right (100,100), I need to find out where that point would be in a rectangle with lower left (0,0) and upper right (500, 500).
I feel like this is just simple math but I'm having a heck of a time getting it right...
It is for a little computer graphics program written in java. Essentially there is a clip window that changes, and that clip window needs to fill the whole view window. Initial values for clip and view are given by the above rectangles, in that order. However, the clip could change to, for example, a rectangle with lower left (-80, -65) and upper right (75, 65). I would then need to convert a point that falls within that rectangle to a point that falls within the view window (lower left (0,0), upper right (500, 500))
Here is what I have for it right now:
public int normalizeX(float x) {
float clipWidth = clipRight - clipLeft;
int viewWidth = viewRight - viewLeft;
x += 100; //Get x into range [0, 200] instead of [-100, 100]
//First convert x to value within clip width, then "scale" to viewport width
return (int)(((clipWidth*x)/200) * (viewWidth/clipWidth));
}
public int normalizeY(float y) {
float clipHeight = clipTop - clipBottom;
int viewHeight = viewTop - viewBottom;
y += 100; //Get y into range [0, 200] instead of [-100, 100]
//First convert y to value within clip height, then "scale" to viewport height
return (int)(((clipHeight*y)/200) * (viewHeight/clipHeight));
}
Thanks for any help!
Assuming your old bounds are xLoOld and xHiOld (-80 and 75, respectively, in your example) and your new bounds are xLoNew and xHiNew (0 and 500, respectively, in your example), then you can normalize your xOld to your new coordinate system like this:
xNew = (xOld-xLoOld) / (xHiOld-xLoOld) * (xHiNew-xLoNew) + xLoNew
Same thing for y.
Something like this may help you.
public static float scale(
float x,
float old_min, float old_max,
float new_min, float new_max)
{
float old_range = old_max - old_min;
float new_range = new_max - new_min;
return new_min + (x - old_min) * new_range / old_range;
}
You will need to scale your coordinates in both dimensions. I've left out other computations such as casting to int, etc.
I also recommend creating suitable types such as Region or Rectangle to simplify your code by, at least, reducing the number of arguments to this function.
Simple as hell: you want a transformation which maps -100 to 0 and 100 to 500. Or put blindly, which maps the range [-100, 100] to the range [0, 500] :
[-100, 100] ----> [0, 500]
First step is to convert the range [-100, 100] to [0, 200]:
x ----> x + 100
Next step is to convert the range [0, 200] to [0, 500]
x ----> x * 500 / 200 = 2.5 * x
In fine, your transformation reads
x ----> 2.5 * (x + 100)
and likewise for y:
y ----> 2.5 * (y + 100).
Hope this makes it clear, and that you will be able to reuse the logic in more complex cases.
The slick and general way to do this is with an affine transform.
You'll have a 2 x 2 matrix (call it A) that characterizes the "stretchiness" of the original field to the second field, and a 2 x 1 matrix (call it b) characterizing the offset.
Then, with x as your (2 x 1) input and y as your (2 x 1) output, it's just y = Ax + b.
The technique also lets you do a lot more (like rotations) but those probably aren't important for your application.
http://en.wikipedia.org/wiki/Affine_transformation