How to Render a Hex Grid - java

I am currently working on a game that requires a hexagon grid to be rendered in order to provide a place for the game to take place. I am using offset coordinates to store the hex grid in memory. When I attempt to render the grid, I get a result like the following with space between the hexes. I want the hexes to perfectly align so that all of the neccessary are at the exact same points, and so that their lines overlap and there is no space between the hexes
The coordinates inside each hex represent the offset coordinates of the hex.
I am using the distances shown in the following image (source) to decide where to render each hex.
The only StackExchange question I was able to find that addresses a problem that sounds like this one is here. It, however, only talks about a data structure to store the hexes in and not how to render them from said structure. I am attempting to render them as described here, however, I get the buggy result shown above.
Here is the portion of my code that handles rendering (it should be easy to infer what the various custom methods do, if there is any confusion please let me know):
#Override
public void paint(Graphics g){
int quarterHexSize = 32; //using non static final member variables because I want to make this dynamically determine the largest size of the hexagons that will fit on the screen later
int halfHexSize = quarterHexSize * 2;
int hexSize = halfHexSize * 2;
int cx, cy, renderX, renderY;
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.WHITE);
for(cx=0;cx<5;cx++){
for(cy=0;cy<5;cy++){
Hex hex = board.getHexAt(cx, cy);
if(hex != null){
renderX = cx * 2; //multiplying by 2 rather than using floats to represent half offset to simplify code and because IIRC integers are faster, I got the same error when using floats
renderY = cy * 2;
if(renderY % 4 != 0){
renderX++;
}
//converts the somewhat arbitrary units into the actual display size units using the values from the image in the question
renderX *= hexSize;
renderY *= quarterHexSize * 3f;
//the numbers are divided by 2 to make up for the offset
renderX /= 2;
renderY /= 2;
//64 is added to both dimensions to shift the grid inside the window
renderX += 64;
renderY += 64;
drawHex(new RenderPoint(renderX, renderY), halfHexSize, g);
g.drawString(cx + ", " + cy, renderX, renderY);
}
}
}
}
private void drawHex(RenderPoint center, int hexSize, Graphics g){
drawHexLine(center, hexSize, 0, 1, g);
drawHexLine(center, hexSize, 1, 2, g);
drawHexLine(center, hexSize, 2, 3, g);
drawHexLine(center, hexSize, 3, 4, g);
drawHexLine(center, hexSize, 4, 5, g);
drawHexLine(center, hexSize, 5, 0, g);
}
private void drawHexLine(RenderPoint center, int hexSize, int firstCornerNum, int secondCornerNum, Graphics g){
RenderPoint firstCornerNumHexPoint = getHexCorner(center, hexSize, firstCornerNum);
RenderPoint secondCornerNumHexPoint = getHexCorner(center, hexSize, secondCornerNum);
g.drawLine(
firstCornerNumHexPoint.getX(), firstCornerNumHexPoint.getY(),
secondCornerNumHexPoint.getX(), secondCornerNumHexPoint.getY()
);
//g.drawString(String.valueOf(firstCornerNum), firstCornerNumHexPoint.getX(), firstCornerNumHexPoint.getY());
}
private RenderPoint getHexCorner(RenderPoint center, int hexSize, int cornerNum){
return RenderPoint.doublesToInts( //simply rounds the provided doubles and creates a RenderPoint object with these new rounded values
center.getX() + hexSize * Math.sin(cornerNum * 60 * 0.0174533), //decimal number converts from degrees to radians
center.getY() + hexSize * Math.cos(cornerNum * 60 * 0.0174533)
);
}

I have determined what the error was. I had missed a specific detail in the webpage when I assumed that the diagram I posted in the question completely explained the placement of the hexagons.
I have changed the variable renderX to:
renderX *= Math.round(HALF_SQRT_THREE * hexSize);
HALF_SQRT_THREE is a constant I defined in the variable to avoid recalculating it each time a hexagon is rendered. It is defined as Math.sqrt(3)/2.0.
Here is the quote from the webpage:
The width of a hexagon is width = sqrt(3)/2 * height. The horizontal distance between adjacent hexes is horiz = width.

Related

2D plotting in Java

Maybe this is only my problem, but I simply can't find this while searching on Google, and it shouldn't be that hard.
I'm looking for a Class/API for 2D plotting.
I need a method in which I give a series of int or double values, and it plots them in a 2-coordinate plane, and draws the plane on a JFrame or JPanel.
Here's a method:
public void plot(String ints, Graphics g) {
ints = "put all nums here (e.g. 4,3;9,1;1.1,2)";
String[] Part1 = ints.split(";");
String coor1 = Arrays.(Part1[0]);
String coor2 = Arrays.(Part1[2]);
g.drawLine(50, 0, 2, heightOfFrame);
g.drawLine(0, 50, widthOfFrame, 2);
g.drawLine(45, 40, 10, 2);
g.drawLine(40, 45, 2, 10);
int coord1 = Integer.parseInt(coor1) * 10;
int coord2 = Integer.parseInt(coor2) * 10;
g.drawOval(coord1-1, coord2-1, 2, 2);
}
In theory, this should work - though I haven't tested it - so please tell me about any bugs in this and I'll fix it.
BTW: this only covers 0 and 1 x and y; but it's the general idea to get you started.

Turn an Integer into a set of pictures

I am making a minecraft mod that implements a new system of "energy" for the player. There are various ways to acquire this energy and I want it to display the player's amount of energy onto the screen. My plan for this is to make a GUI (with OpenGL, as minecraft uses) that uses a file called "energybar.png":
to print numbers. This is the code I have for the method that will do as described.
#SubscribeEvent
public void onGUIRenderEvent(RenderGameOverlayEvent event){
if(event.isCancelable() || event.type != RenderGameOverlayEvent.ElementType.EXPERIENCE)
{
return;
}
int xPos = 10;
int yPos = 10;
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_LIGHTING);
mc.renderEngine.bindTexture(energybar);
String energyString = Integer.toString(Energy.PlayerTotalEnergy);
for(int i=0; i < energyString.length(); i++) {
LogHelper.info("Energy: " + energyString);
drawTexturedModalRect(xPos, yPos, (Energy.PlayerTotalEnergy / (int)Math.pow(10, i))*16, 0, 16, 16);
xPos += 16;
}
}
Each number in the photo is spaced out so it should be in its respective 16 pixels (ie: 0 is x positions 0-16, 1 is x positions 17-32, etc). The photo is 256x256 as defined by the standards of minecraft's GUI system. This is the layout of the method to draw a picture:
void drawTexturedModalRect(int xPosition, int yPosition, int uPosition, int vPosition, int width, int height)
The problem I have with this is that the U Positions for the numbers i need to print onto the screen are not working right.
I have also tried passing:
energyString.substring(i, i)
to a method that takes the substring and converts it back to an integer and multiplies it by 16 to get the uPosition, but when I do the:
String energyString = Integer.toString(Energy.PlayerTotalEnergy);
the Integer.toString() and also String.valueOf() methods have trouble with zeros. For example if Energy.PlayerTotalEnergy was just 0, they would not return a string "0", they just return "".
If someone could help me figure out why I can't get this to work or come up with a better idea of how I can use Minecraft and OpenGL to print this number onto my screen. The reason I'm not just printing it as a number is because I want to keep the red numbers as they look.
This is more of a guess.
It seems to me that if Energy.PlayerTotalEnergy was, let's say, 327, then your uPosition will be:
i=0: u= 327*16
i=1: u= 32*16
i=2: u= 3*16
Did you mean for them to be 7*16, 2*16, and 3*16?
In that case you should mod them with 10:
drawTexturedModalRect(xPos, yPos, ( (Energy.PlayerTotalEnergy / (int)Math.pow(10, i))%10)*16, 0, 16, 16);

How to adjust sin (x) graph coordinates in java to fill only a portion of the window

so currently I am working on a project for class in which I am required to draw the sin(x) function via the drawLine() method. Currently this is the loop I am using to achieve this:
int xShift = getWidth() / 50;
int xShift2 = getWidth() / 100;
int yShift = getHeight() / 10;
int yShift2 = getHeight() / 17;
int xStart = xShift;
int xEnd = xShift;
int yStart = getHeight() / 2;
int yEnd = getHeight() / 2;
int scale = getHeight() / 2;
for (double i = Math.PI / 32; i <= Math.PI * 2; i+= Math.PI / 32){
xEnd += getWidth() / 64;
yEnd = scale - ((int) Math.round(Math.sin(i) * scale));
g.drawLine(xStart, yStart, xEnd, yEnd);
xStart = xEnd;
yStart = yEnd;
}
This outputs something that looks like this:
What I want to change is that the graph will be within the constraints of the dashed blue lines, and it also will go to the end of the grey line where the 2pi marker is (marked by the blue arrow). How can I go about making these changes?
Note: this is what it looks like when I maximize the window:
For some reason the sin graph goes beyond the bounds I want it to.
Thank you for your time and I appreciate any help you can offer.
First, let's see what you are doing with your calculation of Y.
yEnd = scale - ((int) Math.round(Math.sin(i) * scale));
Since the actual sine function goes between -1 and 1, it means that your yEnd will go between scale - scale and scale + scale. This means it will go between 0 (the edge of the window), and 2×scale. Since your scale is half the height of the window, 2×scale means the full height of the window. Again - the edge of the window.
First, think what happens if your scale is smaller. If instead of height/2, the scale is (height/2 - 10), then 2×scale will be the window height - 20. That's more or less the amplitude you want - but it still scale - scale, so it still starts from the edge (try it!). Reducing scale further will lower the amplitude, but still, you will start from the edge.
To prevent that, you should change the formula. It shouldn't be adding the sine to scale. Think: when the sine is -1, you want it to be at the greatest distance from the middle of the window. When it's +1, you want the line to be at the greatest distance, again, from the middle of the window. Now that you changed scale, it's no longer half the height of the window, so you shouldn't use it for your base line.
You should have one parameter that says "what is the base height of my graph", and one parameter that says "what is the maximum amplitude of my graph". The two parameters shouldn't be the same:
int baseHeight = getHeight() / 2;
int amplitude = getHeight() / 2 - getHeight() / 50;
...
// In the loop
yEnd = baseHeight + ((int) Math.round(Math.sin(i) * amplitude));
Play around with these parameters and you'll see how they affect the way your graph is drawn.
Now as for your X. You want to do 64 steps that represent 2π. But if you want the graph to be less wide than the window, the step size cannot be getWidth() / 64. You start width/50 from the edge, add 63 * getWidth() / 64 to that. Since width/50 is more than width/64, you will be drawing more than your width.
So you need to calculate the actual final width of the graph: It's supposed to be the total width, excluding the right and the left margins. So getWidth() - 2 * getWidth()/50 is the actual width, and each step should be 1/64 of that.
int step = ( getwidth() - getWidth() / 25 ) / 64;
...
// In the loop
xEnd += step;

How to draw a triangle at the position which the user clicks on

I'm trying to draw a triangle at the position which the user clicks on.
This is what I've done so far:
int[] xPoints = {(xPosition / 2), xPosition, (xPosition + (xPosition / 2))};
int[] yPoints = {(yPosition + yPosition), yPosition, (yPosition + yPosition)};
g.drawPolygon(xPoints, yPoints, 3);
The problem is that the size of the triangle varies depending on the xPosition and yPosition (these are taken from mouse coordinates).
Any ideas how I can just place a fixed size triangle at the specified X and Y coordinates?
Instead of using xPosition / 2 and yPosition for the first and third points, use a fixed offset from the xPosition like so:
//use whatever size you want
//this will make a triangle with the top at the clicked point
int halfWidth = 50, height = 100;
int[] xPoints = { xPosition - halfWidth, xPosition, xPosition + halfWidth };
int[] yPoints = { yPosition + height, yPosition, yPosition + height };
You can play around with the sizes, but if you want it to be equilateral, then height should be Math.sqrt(3) * halfWidth.
Pick a size and call it SIZE:
int[] xPoints = {xPosition, xPosition, xPosition + SIZE))};
int[] yPoints = {yPosition, yPosition + SIZE, yPosition))};
This will draw a triangle which doesn't change size at different points. However, if you want a certain kind of triangle which points a certain direction, you will need to use some geometry and perhaps trigonometry to do the calculations.

OpenGL Vertex Arrays and GL_TRIANGLE_STRIP drawing error

I'm implementing a height map with GL_TRIANGLE_STRIPS and the LWJGL OpenGL Java Bindings. When I draw 'direct' using 'GL_BEGIN/GL_END' it works perfectly but when the heightmap is to big it works slow.
As I want to move on using VBO's I'm now learning how to use Vertex Array's. This is where I have a problem, the drawing is just wrong. It seems that the last triangle of the strip returns to the first one. Perheps a picture is better:
Good drawing:
Bad Vertex array drawing:
My code is the following for normal drawing:
public void renderDirect() {
//adapt the camera to the map
float scale = 5.0f / Math.max(w - 1, l - 1);
GL11.glScalef(scale, scale, scale);
GL11.glTranslatef(-(float) (w - 1) / 2, 0.0f, -(float) (l - 1) / 2);
//choose map color
GL11.glColor3f(0.3f, 0.9f, 0.0f);
for (int z = 0; z < l - 1; z++) {
//Makes OpenGL draw a triangle at every three consecutive vertices
GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
for (int x = 0; x < w; x++) {
Vector3f normal = getNormal(x, z);
GL11.glNormal3f(normal.getX(), normal.getY(), normal.getZ());
GL11.glVertex3f(x, getHeight(x, z), z);
normal = getNormal(x, z + 1);
GL11.glNormal3f(normal.getX(), normal.getY(), normal.getZ());
GL11.glVertex3f(x, getHeight(x, z + 1), z + 1);
}
glEnd();
}
}
My code is the following for vertex array drawing:
private void loadArrays(){
//calculate the length of the buffers
bLength = (l-1) * w * 6;
//create the normal and vertex buffer array's
dataBuffer = BufferUtils.createFloatBuffer(bLength*2);
cBuffer = BufferUtils.createFloatBuffer(bLength);
for (int z = 0; z < l - 1; z++) {
//Fill up the buffers
for (int x = 0; x < w; x++) {
Vector3f normal = getNormal(x, z);
dataBuffer.put(x).put(getHeight(x,z)).put(z);
dataBuffer.put(normal.getX()).put(normal.getY()).put(normal.getZ());
normal = getNormal(x, z + 1);
dataBuffer.put(x).put(getHeight(x,z+1)).put(z+1);
dataBuffer.put(normal.getX()).put(normal.getY()).put(normal.getZ());
}
}
}
int stride = 6*4;
public void renderDirect() {
//adapt the camera to the map
float scale = 5.0f / Math.max(w - 1, l - 1);
GL11.glScalef(scale, scale, scale);
GL11.glTranslatef(-(float) (w - 1) / 2, 0.0f, -(float) (l - 1) / 2);
//choose map color
GL11.glColor3f(0.3f, 0.9f, 0.0f);
//Draw the vertex arrays
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
dataBuffer.position(0);
glVertexPointer(3, stride, dataBuffer);
dataBuffer.position(3);
glNormalPointer(stride,dataBuffer);
glDrawArrays(GL_TRIANGLE_STRIP, 0, bLength/3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
}
What am I doing wrong?
Concatenating triangle strips back-to-back like that will not work as you expect.
Either call glDrawArrays() in a loop adjusting the first and count parameters to draw your original N triangle strips, or add degenerate triangles to the end of each row to reset the strip starting position.
Or just use GL_TRIANGLES :)
Another solution that can safe you a lot of time if the number of consecutively drawn triangles in your strip is high enough is the following:
When rendering triangle strips you provide a stream of vertex information to OpenGL and it renders a triangle for each new vertex (e.g. sequence ABCDEF gives you the triangles ABC,BCD,CDE,DEF). Now, this will always be a single strip (that's why it's called GL_TRIANGLE_STRIP and noth the common plural naming).
Anyway, if you need to introduce a gap you may use several calls to your draw function (although that is what you want to avoid, right), but you may also make use of invalid polygons.
An invalid polygon is one that has no surface. You can create an invalid polygon by providing identical points like ABB or ABA. As far as I know such a polygon will be rejected quite early in the pipeline and it therefore doesn't give you much overhead. Now to your particular problem. Assume you have a gap (indicated by the vertical bar) like this "ABC|DEF". Modify your stream to look like this "ABCCEEDEF". This abomination gives you the triangles ABC, BCC, CCE, CEE, EDE, DEF. If you account for the early rejection of the invalid ones you get ABC and DEF - exactly what you wanted.
Ergo, in total each gap blows up your vertex stream by three triangles which in turn makes it pretty easy to see when you break even complexity-wise.
Anyway, if you need to build a gap, just you may do so by providing an invalid triangle

Categories