Skip to content

hexagonal lattice rounding

How do you round coordinates on a hexagonal grid?

definitionhexagonal vs. visual space
definitionrendering transformation

Define the transformation T ⁣:H→𝒱T\colon ℋ→𝒱 by

T=[cos⁑0cos⁑π3sin⁑0sin⁑π3]=[112032]. T = \begin{bmatrix} \cos 0 & \cos \frac Ο€ 3 \\ \sin 0 & \sin \frac Ο€ 3 \end{bmatrix} = \begin{bmatrix} 1 & \frac 1 2 \\ 0 & \frac {\sqrt 3} 2 \end{bmatrix}.

This is the transformation that maps points in hexagonal-space coordinates to their visual positions. In particular, T(Z2)T(β„€Β²) looks like this (dotted lines annotate hexagonal boundaries):

(insert diagram)

For any p∈Hpβˆˆβ„‹ and qβˆˆπ’±qβˆˆπ’±, we will say that pp renders to qq to mean Tp=qTp=q; i.e., qq is the visual position of pp.

problemhexagonal rounding

Given p∈Hpβˆˆβ„‹, find pβ€²βˆˆZ2βŠ‚Hp'βˆˆβ„€Β²βŠ‚β„‹ so that the (Euclidean) distance between Tp,Tpβ€²Tp,Tp' is minimized. That is to say, we wish to round pp to the nearest point on the hexagonal lattice.

Why is this not a trivial question? Why is the answer not just

(x,y)↦([x],[y]) (x,y) ↦ ([x], [y])

(apply regular integer rounding to each coordinate)? The problem with this is that it minimizes distance only in regular orthogonal Cartesian spaceβ€”we’d like to minimize distance in hexagonal space. This is best seen by plotting what it does visually:

DIAGRAM

In particular, in plain R2ℝ² this collects points into square boxes, which after applying the render transformation turns into rhombi. So this clearly fails, because we’re looking for an algorithm that collects points into hexagonal boxes.

(For an explicit counterexample, take some small Ο΅>0Ο΅>0, consider the point p≔(12βˆ’Ο΅,12βˆ’Ο΅)∈Hp≔(\frac12-Ο΅,\frac12-Ο΅)βˆˆβ„‹, whose visual position is Tpβ‰ˆ(32,32)Tpβ‰ˆ(\frac32,\frac{\sqrt 3}2). Naively, this point rounds to (0,0)(0,0); however it is visually actually closer to (1,0)(1,0) or (0,1)(0,1).)


OK, so how do we tackle this? Here’s the approach I came up withβ€”probably not the most elegant, but I think it’s cute.

So, let p=(x,y)p=(x,y) be given. We will denote (u,v)≔Tp(u,v)≔Tp and also let pβ€²=(xβ€²,yβ€²)p'=(x',y') denote the hexagonal-rounded coordinates we are trying to calculate. We will call xβ€²x' the column and yβ€²y' the row. Consider two cases based on yy:

  1. If yy is less than 13\frac13 distance away from its nearest integer, then we say pp is in a vertical section of the grid. In these sections the hexagonal boundaries are purely vertical (and column differentiation depends only on the visual position uu). This is the simple case.

    We know unambiguously that the row is yβ€²=[y]y'=[y]. To calculate the column, observe that the lattice point at column 00 renders to T(0,yβ€²)=(yβ€²2,3yβ€²2)T(0,y')=(\frac{y'}2,\frac{\sqrt 3 y'}2). So, relative to column zero, qq is horizontally offset by uβˆ’yβ€²2u-\frac{y'}2, whose nearest integer gives the column number xβ€²x'.

    (xβ€²,yβ€²)=(uβˆ’[y]2,[y]).(x',y') = \left(u - \frac {[y]} 2, [y]\right).
  2. If yy is more than 1/31/3 distance away from its nearest integer, we say pp is in a zigzag section of the grid. In these sections we’re trying to differentiate between adjacent rows of hexagons, demarcated by the top/bottom boundaries of hexagons, which are slanted and zigzag-shaped.

    To make these regions easier to analyze, let’s transform into yet another space, in which the zigzag hexagon boundaries become purely horizontal or vertical. Define S ⁣:Hβ†’R2S\colon ℋ→ℝ² by

    S=[1βˆ’112], S = \begin{bmatrix} 1 & -1 \\ 1 & 2 \end{bmatrix},

    Observe that under this mapping, the vectors parallel to the zigzag boundaries map to horizontal/vertical directions: S(2,βˆ’1)=(3,0)S(2,-1)=(3,0), S(1,1)=(0,3)S(1,1)=(0,3). Here’s how the section 1/3<y<2/31/3<y<2/3 looks under this mapping:

    DIAGRAM

    The neat thing to observe here is that, within the zigzag sections, the original hexagonal cutouts are now just unit half-squares. So we floor each coordinate, sending each square to its bottom-left corner, which we denote ⌊SpβŒ‹βŒŠSpβŒ‹.

    Back in visual space, each of these corners corresponds to the target hexagon’s left vertex along the zigzag. So let

    (a,b)≔VSβˆ’1⌊SpβŒ‹. (a,b) ≔ V S⁻¹ ⌊SpβŒ‹.

    At this point the vertical coordinate rounds to the row number yβ€²=[b]y'=[b], and the horizontal component shifted to the right by 1/21/2 aligns with the center of the target hexagon, i.e., the column number xβ€²=a+1/2x'=a+1/2.