Friday, May 29, 2020

Grid

Meta

I'm sure no one actually cares, but I apologize for the delay in this week's post. I started a new job this week, so I've been a little drained from getting adjusted to that.

Grid

Last week I discussed the next functionality to tackle. I concluded that implementing a grid would be a good easy-win to allow myself more of a fun/work balance. The grid basically took me the weekend to get right:
Actually, before I implemented the grid I finally closed out my story for selecting lines; what I had worked on before was individual vertices, but that functionality laid the groundwork for selecting line, triangle, and other types of selection handles.

I've implemented a grid a few times before. It's been tricky every time, but it basically boils down to a couple of things:

  1. Knowing how many lines in each direction to draw
  2. Knowing where to draw them
So, from a technical standpoint it actually boils down to one core thing:
  1. Knowing where in (literal game) world the screen extents (left, right, bottom, and top edges) are.
Knowing the above with a given grid spacing (the units between the lines) gives us enough information to know exactly what lines to draw and where. To determine how many lines to draw, all is needed is to take the screen size and divide it by the grid spacing:
  • verticalLines = (screenWidth / gridSpacing)
  • horizontalLines = (screenHeight / gridSpacing)
The vertical lines are the lines positioned across the screen, so each one will have the same y values with varying x values. Conversely, the horizontal lines are the lines positioned down (or up, as my code actually does) the screen, so each one will have the same x values with varying y values.

Those x and y values can be determined from the Camera object, as it contains screen extent values that are updated every time the camera is moved (or zoomed, as I'll get to later). The camera is configured with the screen resolution and assumes its own position as the lower-left corner of the screen, making those calculations very simple:
  • left = cameraPosition.x
  • right = (cameraPosition.x + screenWidth)
  • bottom = cameraPosition.y
  • top = (cameraPosition.y + screenHeight)
These values are also used in the vertex shader to make objects actually move across the screen with the camera (so they don't have to manage that "visual state" manually).

To summarize up to this point:
  1. The camera contains a position and manages the screen extents (the left, right, bottom, and top edges of the screen positioned in the game world) using the pre-configured screen size.
  2. The number of vertical lines on the screen is determined by the screen width divided by the grid spacing.
  3. The number of horizontal lines on the screen is determined by the screen height divided by the grid spacing.
  4. Objects' positions on the screen are modified in the vertex shader to reflect camera movement so their "visual positions" don't have to be managed manually.

Consider the example of an unmoved camera with a screen size of 100x100 and a grid spacing of 10:
  • The camera position would be (0, 0).
  • The screen extents (left, right, bottom, top) would be: (0, 100, 0, 100).
  • The number of vertical lines would be: 10 (100 / 10).
  • The number of horizontal lines would be: 10 (100 / 10).
All vertical lines will start at the y-value of 0 and end at 100, but what are the x-values for them? Intuitively, we can see that there should be vertical lines at: 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100. What if we start at the first x-value of screenExtents.left, and then incrementally add the gridSpacing for verticalLines count of lines?

for (i = 0, x = screenExtents.left; i < verticalLines; i++, x += gridSpacing) { verticalLine at x; }

That would give us vertical lines at: 0, 10, 20, 30, 40, 50, 60, 70, 80, 90
What about the one at 100? Well, it seems we needed to account for 0 in our vertical line count (which you might've also noticed above), making verticalLines = (screenWidth / gridSpacing) + 1

The same pattern would follow for horizontal lines.

Now consider the example of a camera moved to (-5, -15) with the same screen size of 100x100 and grid spacing of 10:
  • The camera position would be (-5, -15).
  • The screen extents (left, right, bottom, top) would be: (-5, 95, -15, 85).
  • The number of vertical and horizontal lines would still be 10 each.
Using the same for-loop above, we would get vertical lines at: -5, 15, 25, 35, 45, 55, 65, 76, 85, 95, 105
(Notice that the one at 105 would be outside the visible extents of the screen, but that's okay because we're talking about two triangles in the end.)

Keeping in mind that the left edge of the screen is at -5 in the world,  it's evident that the grid moves with the camera--which is not what we want.

The remedy to this is actually a simple adjustment to our calculation:
verticalLineStart = floor(screenExtents.left / gridSpacing) * gridSpacing
or applied:
verticalLineStart = floor(-5 / 10) * 10 = floor(-0.5) * 10 = -1 * 10 = -10

And then modify our for-loop to:
for (i = 0, x = verticalLineStart; i < verticalLines; i++, x += gridSpacing) { verticalLine at x; }
which gives us vertical lines at: -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90

The horizontal lines would follow the exact same formula, substituting the verticalLines with horizontalLines, screenExtents.left with screenExtents.bottom, and making horizontalLines at y instead of verticalLines at x.


Zooming aside, this gives us a functional grid that can be configured with a variable grid spacing and adjusts itself to the camera. Seeing as I'm about 40% done with the zoom functionality (hopefully the worst 40% too), I'll leave the zoom logic for next week's post.

No comments:

Post a Comment