JavaFX: Polyline scaling / zooming - java

this is for homework.
We have to create a JavaFX application, complying to the MVP principle, that shows a static sine-wave, with sliders to control the properties of said sine-wave.
These sliders are the amplitude, frequency, phase, and zoom. They're bound, through the presenter, to properties in my model that make up the sine-wave. These then have listeners to update the model on changes.
For drawing my sine-wave, I chose a polyline and I calculate the X and Y coordinates for each point to a observable list in my model:
for (double x = -360; x < 360; x++) {
data.add(x);
data.add(Math.sin(frequency.doubleValue() * x + phase.doubleValue()) * amplitude.doubleValue());
}
Then I reach this dataset to my view through the presenter where I give each point to my polyline:
public void setPoints(ObservableList<Double> list) {
functionLine.getPoints().clear();
functionLine.getPoints().addAll(list);
double temp;
for(int i = 0;i<functionLine.getPoints().size();i++) {
//separate x from y coordinates
if (i % 2 == 0) {
temp = functionLine.getPoints().get(i);
functionLine.getPoints().set(i, temp + (display.getWidth() / 2)); // + displaywidth/2 to get it to the center of the graph
} else {
temp = functionLine.getPoints().get(i);
functionLine.getPoints().set(i, temp + ((display.getHeight() / 2))); //same as above
}
}
}
This also doesn't perform very well because of the for-loop and the interface is laggy, but that's not why I am here.
This is what is currently looks like. The polyline and graph (two lines) are located in its own pane:
Now I have tried to also add zoom to this without increasing the width of the actual line, but I can't figure out how to properly scale around the center of my graph. Obviously I have to transform the coordinates of each point, but I don't know how. I have tried several things but it doesn't achieve what I want.
Feels like something I should be able to do on my own, but I can't apparently.
Any help would be appreciated.

Related

Libgdx/Java: After converting GPS to pixelcoordinates, route is rotated 90°

I had written before about implementing a map in my libgdx project.
I did that using a snapshot of said google map, importing the GPS bounds of the snapshot, the route-latlong values, a locationservice (via interface) and the snapshot as Gdx.files.local string.
Hopefully, the last issue I have right now is that the route is rotated about 45 degrees clockwise. Otherwise my 'enemies' walk a perfect path. I already figured out that I had to 'flip' my y-axis; before that it was rotated AND flipped upside down.
I was hoping someone here with more experience might have dealt with something similar before and has some advice :)
This is basically the code that creates a Waypoint array, after converting the GPS coordinates to pixel-coordinates that correspond to the gps-bounds of the map-snapshot (bottom-left-corner and upper-right-corner see here, as well as the width and height of the map-texture.
private void convertPathToScreen(double[] gpsRoute){
for(int i = 0; i<gpsRoute.length; i++){
if(i % 2 != 0) {
screenRouteCoordinates[i] =
x_GpsToScreenConversion(gpsRouteCoordinates[i]);
}
else{
screenRouteCoordinates[i] =
y_GpsToScreenConversion(gpsRouteCoordinates[i]);
}
}
}
public int x_GpsToScreenConversion(double x_pointInGpsCoords){
double percentage = 1 - Math.abs((x_pointInGpsCoords - x_GpsMin) /
(x_GpsMax - x_GpsMin));
return (int)((percentage* Math.abs(mapWidth - mapOrigin)) + mapOrigin);
}
public int y_GpsToScreenConversion(double y_pointInGpsCoords){
double percentage = (y_pointInGpsCoords - y_GpsMin) / (y_GpsMax - y_GpsMin);
return (int)((percentage* Math.abs(mapHeight - mapOrigin)) + mapOrigin);
}
Edit: Now that I think of it, the error might be in my pathfinding code, although I tested it before moving my project forward and it worked solidly for all values I put in. Anyway, for completness...es sake
private void calculatePathing(){
angle = (float) (Math.atan2(waypointsToGoal[waypoint].y - getY(), waypointsToGoal[waypoint].x - getX()));
velocity.set((float) Math.cos(angle) * speed, (float) Math.sin(angle) * speed);
}
So the question is basically: How do I fix the 90° clockwise rotation that buggers up my game? Can I rotate the coordinates of the array around the center of the map (where all enemies walk to) or is there a mistake in the conversion-code here?
Solution: (Patchwork, but it gets the job done!)
I simply rotated my waypoints by the degree I needed around the destination-point. It doesn't solve the underlying issue, but it solves the symptom.
private void createWaypointArray(){
//formula requires radians
double angle = Math.toRadians(90);
double current_x;
double current_y;
// waypointCache.size()-1 gets me the last waypoint, the destination around which I rotate
double center_x = waypointCache.get(waypointCache.size()-1).x;
double center_y= waypointCache.get(waypointCache.size()-1).y;
// Loop through Vector2 Array, rotate the points around destination and save them
for(int i = 0; i < waypointCache.size()-1; i++){
current_x = waypointCache.get(i).x;
current_y = waypointCache.get(i).y;
waypointCache.get(i).x = (float)((current_x-center_x) * Math.cos(angle) - (current_y-center_y) * Math.sin(angle) + center_x);
waypointCache.get(i).y = (float)((current_x-center_x) * Math.sin(angle) + (current_y-center_y) * Math.cos(angle) + center_y);
// this does work, but also translates the points because it rotates around the
// worldaxis, usable when points lie on normal kartesian axis I guess
// waypointCache.get(i).rotate(90);
}
this.wayPointArray = waypointCache.toArray(new Vector2[waypointCache.size()]);
}

Render multiple heights in an isometric tile based map

Good evening everyone.
I've been messing a bit with isometric tile worlds and I have a few doubts about rendering the elements on it.
When I build a single height map I render it first and then add the diferent elements on top, and the displacement of the last seem right.
public void render(Graphics2D g2d) {
for(int i = 0; i < tileGrid.length; i++) {
Point isop = NeoMath.getInstance().cartToIso(i % yAmmount, i / yAmmount, GlobalDataStorage.tileWidth, GlobalDataStorage.tileHeight);
TileManager.getInstance().getTileByID(tileGrid[i]).render(g2d, isop.x, isop.y);
}
for(Entity entity : entityList) {
entity.render(g2d);
}
}
(The position of the entity is calculated inside it's update).
With this I have no problems as everything is rendered on the same height, the problem comes when I try to add other floors to it.
Let's say that I want it to have three heights. I have a list of list of tiles instead of the single array, and render every element on them:
public void render(Graphics2D g2d) {
int flag = 0;
for(int i = 0; i < tileGrid.size(); i++) {
Point isop = NeoMath.getInstance().cartToIso(i % yAmmount, i / yAmmount, GlobalDataStorage.tileWidth, GlobalDataStorage.tileHeight);
for(int k = 0; k < tileGrid.get(i).size(); k++) {
TileManager.getInstance().getTileByID(tileGrid.get(i).get(k)).render(g2d, isop.x, isop.y - (GlobalDataStorage.tileZ * k));
}
while(flag < currentList.size() &&
currentList.get(flag).getPosition().equals(new Point(i % yAmmount, i /
yAmmount))) {
currentList.get(flag).render(g2d);
flag++;
}
}
}
Where the currentList is the list of entities.
With this I have the problem that, when the entities move to a new position, they get overlaped by the tiles, as these are rendered after the entity, and the position of the entity does not change until it reached the destiny. I could change the position to the new one before rendering, but that implies that in the other axis the previous tile get rendered after the entity, making it disapear for a second due to the overlap.
This also mess when I try to draw selection rectangle as it get stuck behind the tiles being rendered. I don't want them to overlap the whole map so can't draw them after all the rendering has been done either.
Does someone know of another approach that I can try out?
Thank you beforehand.
Draw your entire floor layer in a first pass. Then in the second pass draw all walls and objects and moving entities.
I could change the position to the new one before rendering,
David Brevik, programmer on Diablo, mentions using this option in his GDC talk Diablo: A Classic Games Postmortem. It was his first "What Went Wrong" example!
Reference: https://www.youtube.com/watch?v=VscdPA6sUkc&t=20m17s
Turns out this is a classic hurdle in isometric games.

Generating an N x N grid

What is the most painless way to create an N x N grid in a JavaFX application?
The only requirements I'm looking for is that the size of the grid will always take up the same amount of space, so more squares = smaller squares. I can set colors for the squares, and I can hover over each square individually and be able to show some for each square.
I won't know 'N' until the program runs and parses through some data to figure out how many total squares I need which is when I calculate the smallest NxN grid I can use.
From what I can tell my options are:
GridPane - Using the column constraints and row constraints to generate size and possibly add properties for hovering?
TableView - A lot more options for being able to give each cell data to show when hovered over but still difficult to just add rows and columns to start with.
Rectangles - Just generate and draw each rectangle while calculating the x and y coordinates for each square. This will make it easy to do the colors and hovering but I can't see how resizing would work but I'm ok with having a specific size for my application. I'll also have to calculate the best size to make each square to fill up the grids area.
I'm not necessarily looking for someone to code a solution, but if someone has dealt with this and knows a good way I'd like to hear about it.
Don't stray away from the original ideas. Why are you looking for "painless" ways when all the methods you've given are all viable? Here's one using your rectangles. The GridMaker.SCREEN_SIZE refers to the size of the screen you must have.
public static Pane makeGrid(int n){
double width = GridMaker.SCREEN_SIZE/n;
Pane p = new Pane();
Rectangle [][] rec = new Rectangle [n][n];
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
rec[i][j] = new Rectangle();
rec[i][j].setX(i * width);
rec[i][j].setY(j * width);
rec[i][j].setWidth(width);
rec[i][j].setHeight(width);
rec[i][j].setFill(null);
rec[i][j].setStroke(Color.BLACK);
p.getChildren().add(rec[i][j]);
}
}
return p;
}
Then simply add the mouse listener to the pane if you wish to make it change color.
p.setOnMouseClicked(new EventHandler <MouseEvent> (){
#Override
public void handle(MouseEvent me){
double posX = me.getX();
double posY = me.getY();
int colX = (int)(posX / width);
int colY = (int) (posY / width);
rec[colX][colY].setFill(Color.RED);
}
});
-- Edit
1)
2) For Hover, what kind of hover effects are you looking for? You can add Hover effects onto each rectangles, if you want me to show you how, I can definitely code it for you.

Slick is getting very slow but only drawin rectangles

I am using slick for java since a few days and got a serious problem.
If i run a completely empty apllication (it just shows the fps) with a solution of 800x600 i get a fps count between 700 and 800.
If I now draw an array with 13300 entries as a grid of green and white rectangles, the fps drop to something around 70.
With more entries in the array it becomes really slow.
For example in a solution of 1024x768 and an array with 21760 entries the fps drop to 40.
How i draw a single entry:
public void draw(Graphics graphics){
graphics.setColor(new Color(getColor().getRed(), getColor().getGreen(), getColor().getBlue(), getColor().getAlpha()));
graphics.fillRect(getPosition().x, getPosition().y, getSize().x, getSize().y);
Color_ARGB white = new Color_ARGB(Color_ARGB.ColorNames.WHITE);
graphics.setColor(new Color(white.getRed(), white.getGreen(), white.getBlue(), white.getAlpha()));
}
And this is how I draw the complete array:
public void draw(Graphics graphics) {
for (int ix = 0; ix < getWidth(); ix++) {
for (int iy = 0; iy < getHeight(); iy++) {
getGameGridAt(ix, iy).draw(graphics);
}
}
}
In my opinion 21760 is not that much.
Is there anything wrong with my code or is slick just too slow to draw so much rectangles?
You only want to draw rectangles that are on the screen. If your screen bounds go from 0 to 1024 in the x direction and from 0 to 768 in the y direction, then you only want to loop through rectangles that are inside those bounds and then only draw those rectangles. I can't imagine you are trying to draw 21760 rectangles inside those bounds.
If you are, then try creating one static rectangle and then just try drawing that ONE in all of the different positions you need to draw it at rather than creating a new one every time. For example, in a game I am making, I might have 1000 tiles that are "grass" tiles, but all 1000 of those share the same static texture. So I only need to reference one image rather than each tile creating its own.
Each rectangle can still have a unique state. Just make your own rectangle class and have a static final Image that holds a 5*5 image. Each rectangle will use this image when it needs to be drawn. You can still have unique properties for each rectangle. For example, private Vector2f position, private boolean isAlive, etc
You're probably not going to be able to draw individual rectangles any faster than that.
Games that render millions of polygons per second do so using vertex buffer objects (VBO). For that, you'll probably need to code against the OpenGL API (LWJGL) itself, not a wrapper.
Not sure if Slick will allow it, but if this thing looks anything like a chessboard grid... you could draw just 4 rectangles, grab them and use the resulting image as a texture for your whole image. I'm not even a java programmer just trying to come up with a solution.
Since you're only repeatedly using just a few colors creating a new Color object for every single one is bound to be slow... use new only once for each different color used and store the re-usable colors somewhere in your class, than call the functions with those, constantly allocating and freeing memory is very slow.
And while this might not be as much a benefit as not using new each time but have you considered caching the results of all those function calls and rewriting code as
public void draw(Graphics graphics) {
int ixmax = getWidth();
int iymax = getHeight();
for (int ix = 0; ix < ixmax; ix++) {
for (int iy = 0; iy < iymax; iy++) {
getGameGridAt(ix, iy).draw(graphics);
}
}
}
Or if you'd prefer not to declare new variables
public void draw(Graphics graphics) {
for (int ix = getWidth() - 1; ix >= 0; ix--) {
for (int iy = getHeight() - 1; iy >= 0; iy--) {
getGameGridAt(ix, iy).draw(graphics);
}
}
}
Just noticed in another answer you have an integral size grid (5x5) ... in this case the fastest way to go about this would seem to be to draw each item a single pixel (you can do this directly in memory using a 2-dimensional array) and scale it to 500% or use it as a texture and draw a single rectangle with it the final size you desire ... should be quite fast. Sorry for all the confusion caused by previous answers, you should have said what you're doing more clearly from the start.
If scaling and textures are not available you can still draw in memory using something like this (written in c++, please translate it to java yourself)
for( int x = 0; x < grid.width(); x++ ) {
for( int y = 0; y < grid.height(); y++ ) {
image[x*5][y*5] = grid.color[x][y];
image[x*5][y*5 + 1] = grid.color[x][y];
image[x*5][y*5 + 2] = grid.color[x][y];
image[x*5][y*5 + 3] = grid.color[x][y];
image[x*5][y*5 + 4] = grid.color[x][y];
}
memcpy(image[x*5+1], image[x*5], grid.height() * sizeof(image[0][0]) );
memcpy(image[x*5+2], image[x*5], grid.height() * sizeof(image[0][0]) );
memcpy(image[x*5+3], image[x*5], grid.height() * sizeof(image[0][0]) );
memcpy(image[x*5+4], image[x*5], grid.height() * sizeof(image[0][0]) );
}
I'm not sure, but perhaps for graphics the x and y might be represented in the reversed order than used here, so change the code accordingly if it that's the case (you'll figure that out as soon as a few iterations run), also your data is probably structured a bit differently but I think the idea should be clear.

How to have a "Camera" only show a portion of a loaded area

I'm having a little problem with figuring something out (Obviously).
I'm creating a 2D Top-down mmorpg, and in this game I wish the player to move around a tiled map similar to the way the game Pokemon worked, if anyone has ever played it.
If you have not, picture this: I need to load various areas, constructing them from tiles which contain an image and a location (x, y) and objects (players, items) but the player can only see a portion of it at a time, namely a 20 by 15 tile-wide area, which can be 100s of tiles tall/wide. I want the "camera" to follow the player, keeping him in the center, unless the player reaches the edge of the loaded area.
I don't need code necessarily, just a design plan. I have no idea how to go about this kind of thing.
I was thinking of possibly splitting up the entire loaded area into 10x10 tile pieces, called "Blocks" and loading them, but I'm still not sure how to load pieces off screen and only show them when the player is in range.
The picture should describe it:
Any ideas?
My solution:
The way I solved this problem was through the wonderful world of JScrollPanes and JPanels.
I added a 3x3 block of JPanels inside of a JScrollPane, added a couple scrolling and "goto" methods for centering/moving the JScrollPane around, and voila, I had my camera.
While the answer I chose was a little more generic to people wanting to do 2d camera stuff, the way I did it actually helped me visualize what I was doing a little better since I actually had a physical "Camera" (JScrollPane) to move around my "World" (3x3 Grid of JPanels)
Just thought I would post this here in case anyone was googling for an answer and this came up. :)
For a 2D game, it's quite easy to figure out which tiles fall within a view rectangle, if the tiles are rectangular. Basically, picture a "viewport" rectangle inside the larger world rectangle. By dividing the view offsets by the tile sizes you can easily determine the starting tile, and then just render the tiles in that fit inside the view.
First off, you're working in three coordinate systems: view, world, and map. The view coordinates are essentially mouse offsets from the upper left corner of the view. World coordinates are pixels distances from the upper left corner of tile 0, 0. I'm assuming your world starts in the upper left corner. And map cooridnates are x, y indices into the map array.
You'll need to convert between these in order to do "fancy" things like scrolling, figuring out which tile is under the mouse, and drawing world objects at the correct coordinates in the view. So, you'll need some functions to convert between these systems:
// I haven't touched Java in years, but JavaScript should be easy enough to convey the point
var TileWidth = 40,
TileHeight = 40;
function View() {
this.viewOrigin = [0, 0]; // scroll offset
this.viewSize = [600, 400];
this.map = null;
this.worldSize = [0, 0];
}
View.prototype.viewToWorld = function(v, w) {
w[0] = v[0] + this.viewOrigin[0];
w[1] = v[1] + this.viewOrigin[1];
};
View.prototype.worldToMap = function(w, m) {
m[0] = Math.floor(w[0] / TileWidth);
m[1] = Math.floor(w[1] / TileHeight);
}
View.prototype.mapToWorld = function(m, w) {
w[0] = m[0] * TileWidth;
w[1] = m[1] * TileHeight;
};
View.prototype.worldToView = function(w, v) {
v[0] = w[0] - this.viewOrigin[0];
v[1] = w[1] - this.viewOrigin[1];
}
Armed with these functions we can now render the visible portion of the map...
View.prototype.draw = function() {
var mapStartPos = [0, 0],
worldStartPos = [0, 0],
viewStartPos = [0, 0];
mx, my, // map coordinates of current tile
vx, vy; // view coordinates of current tile
this.worldToMap(this.viewOrigin, mapStartPos); // which tile is closest to the view origin?
this.mapToWorld(mapStartPos, worldStartPos); // round world position to tile corner...
this.worldToView(worldStartPos, viewStartPos); // ... and then convert to view coordinates. this allows per-pixel scrolling
mx = mapStartPos[0];
my = mapStartPos[y];
for (vy = viewStartPos[1]; vy < this.viewSize[1]; vy += TileHeight) {
for (vx = viewStartPos[0]; vx < this.viewSize[0]; vy += TileWidth) {
var tile = this.map.get(mx++, my);
this.drawTile(tile, vx, vy);
}
mx = mapStartPos[0];
my++;
vy += TileHeight;
}
};
That should work. I didn't have time to put together a working demo webpage, but I hope you get the idea.
By changing viewOrigin you can scroll around. To get the world, and map coordinates under the mouse, use the viewToWorld and worldToMap functions.
If you're planning on an isometric view i.e. Diablo, then things get considerably trickier.
Good luck!
The way I would do such a thing is to keep a variable called cameraPosition or something. Then, in the draw method of all objects, use cameraPosition to offset the locations of everything.
For example: A rock is at [100,50], while the camera is at [75,75]. This means the rock should be drawn at [25,-25] (the result of [100,50] - [75,75]).
You might have to tweak this a bit to make it work (for example maybe you have to compensate for window size). Note that you should also do a bit of culling - if something wants to be drawn at [2460,-830], you probably don't want to bother drawing it.
One approach is along the lines of double buffering ( Java Double Buffering ) and blitting ( http://download.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html ). There is even a design pattern associated with it ( http://www.javalobby.org/forums/thread.jspa?threadID=16867&tstart=0 ).

Categories