Reputation: 1098
I am trying to build a hex grid by following along with Red Blob Games. The hex grid axial coordinate system runs from -radius < x < radius
and -radius < y < radius
. Red Blob defines a function that converts the int
axial coordinates into a double
pixel coordinate.
public Point tileToPixel(Tile tile) {
Hex hex = (Hex) tile;
double x = (orientation.getF0() * hex.getX() + orientation.getF1() * hex.getY()) * size.getX();
double y = (orientation.getF2() * hex.getX() + orientation.getF3() * hex.getY()) * size.getY();
return new Point(x, y);
}
Point
is equivalent to Point2D
; I decided to write my own.
orientation
is an enum
that defines the points of the hexagon based on a flat-top or point-top hexagon orientation.
POINT_TOP_ORIENTATION ( Math.sqrt(3.0), Math.sqrt(3.0)/2.0, 0.0, 3.0/2.0,
Math.sqrt(3.0)/3.0, -1.0/3.0, 0.0, 2.0/3.0,
0.5),
FLAT_TOP_ORIENTATION ( 3.0/2.0, 0.0, Math.sqrt(3.0)/2.0, Math.sqrt(3.0),
2.0/3.0, 0.0, -1.0/3.0, Math.sqrt(3.0)/3.0,
0.0);
private HexOrientation(double f0, double f1, double f2, double f3,
double b0, double b1, double b2, double b3,
double startAngle) {
this.f0 = f0;
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
this.b0 = b0;
this.b1 = b1;
this.b2 = b2;
this.b3 = b3;
this.startAngle = startAngle;
}
The problem becomes that the axial coordinate is converted to a pixel coordinate double
that includes negative values and assumes a normal Cartesian format (positive Y up, positive X right) with the origin being (0,0) at the center.
I am using Scene Builder to construct my display. Here is my extremely basic set-up:
You can see that I have the AnchorPane
as root, a BorderPane
, and a Pane
located in the center of the BorderPane
where I intend my Hexagons to be displayed.
My Main
class is defined:
public class Main extends Application {
@Override
public void start(Stage stage) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/Display.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setTitle("Grid Display");
stage.setScene(scene);
stage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
My FXML class is defined:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.ScreenController">
<children>
<BorderPane layoutX="-8.0" layoutY="-69.0" prefHeight="297.0" prefWidth="610.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<center>
<Pane fx:id="nodePane" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
</children>
</AnchorPane>
Lastly, my controller class, ScreenController
is defined:
public class ScreenController {
@FXML
private Pane nodePane;
@FXML
protected void initialize() {
Grid grid = new Grid(new HexScreen(HexOrientation.POINT_TOP_ORIENTATION, new Point (10,10)), new HexMap(MapShape.HEXAGON));
for(Tile tile : grid.getMap().getMap()) {
int i = 0;
Double[] points = new Double[12];
Point[] corners = grid.getScreen().polygonCorners(tile);
for(Point point : corners) {
points[i] = point.getX();
points[i+1] = point.getY();
i += 2;
}
Polygon polygon = drawTile(points);
if (tile.getX() == 0 && tile.getY() == 0) {
polygon.setFill(Color.BLACK);
}
this.nodePane.getChildren().add(polygon);
}
}
private Polygon drawTile(Double[] points) {
Polygon polygon = new Polygon();
polygon.getPoints().addAll(points);
polygon.setStroke(Color.BLACK);
polygon.setFill(Color.TRANSPARENT);
return polygon;
}
}
The output of this is:
How can I set the origin of the nodePane
to be in the center of the Pane
rather than the top left? I would rather not recalculate the pixel coordinates via the tileToPixel()
because this function is located in a project that I import as a JAR into the GridDisplay
project where the actual JavaFX display is occurring. I intend to be able to use that JAR as a library independent of how I am building the GUI and thus need "universal" tile to pixel calculations and not JavaFX specific (if that makes sense).
Therefore, I think the most appropriate place to change the pixel coordinates is in the ScreenController
. So far I have tried:
for(Tile tile : grid.getMap().getMap()) {
int i = 0;
Double[] points = new Double[12];
Point[] corners = grid.getScreen().polygonCorners(tile);
for(Point point : corners) {
points[i] = point.getX();
points[i+1] = point.getY();
i += 2;
}
Polygon polygon = drawTile(points);
if (tile.getX() == 0 && tile.getY() == 0) {
polygon.setFill(Color.BLACK);
}
polygon.setTranslateX(nodePane.getWidth()/2);
polygon.setTranslateY(nodePane.getHeight()/2);
this.nodePane.getChildren().add(polygon);
}
The output of this looks exactly the same as the above image. I have also substituted nodePane.getWidth()
and nodePane.getHeight()
for:
polygon.setTranslateX(nodePane.getPrefWidth()/2);
polygon.setTranslateY(nodePane.getPrefHeight()/2);
This moves the origin slightly but not how I imagine it should look. For reference, the PrefWidth
and PrefHeight
for my AnchorPane
, BorderPane
, and Pane
are all set at USE_COMPUTED_SIZE
.
Finally, even if the above solution at centered my origin at the center of the Pane
, I don't believe it would work if a user resized the window.
Thank you for reading my lengthy post, please let me know if you require any additional information.
Upvotes: 2
Views: 2810
Reputation: 1098
@Sedrick thank you for your help.
Here is my solution.
public class ScreenController {
@FXML
private StackPane nodePane;
@FXML
protected void initialize() {
Grid grid = new Grid(new HexScreen(HexOrientation.POINT_TOP_ORIENTATION, new Point (10,10)), new HexMap(MapShape.HEXAGON));
Group group = new Group();
for(Tile tile : grid.getMap().getMap()) {
int i = 0;
Double[] points = new Double[12];
Point[] corners = grid.getScreen().polygonCorners(tile);
for(Point point : corners) {
points[i] = point.getX();
points[i+1] = point.getY();
i += 2;
}
Polygon polygon = drawTile(points);
if (tile.getX() == 0 && tile.getY() == 0) {
polygon.setFill(Color.BLACK);
}
group.getChildren().add(polygon);
}
this.nodePane.getChildren().add(group);
}
private Polygon drawTile(Double[] points) {
Polygon polygon = new Polygon();
polygon.getPoints().addAll(points);
polygon.setStroke(Color.BLACK);
polygon.setFill(Color.TRANSPARENT);
//tileHandler.hoverHandler(polygon, Color.TRANSPARENT, Color.RED);
return polygon;
}
}
I first put all the polygons (hexs) into a Group
object. Then added the Group
to a StackPane
(instead of a Pane
), as suggested above. This solution did not work if I used a Pane
instead of a Group
. Here is the resultant output:
I still need to mirror the grid across the X axis (because -Y is still up) and preferably allow for dynamic resizing of the grid with window resizing so the full grid is always in view.
Upvotes: 3