Skip to content

Commit

Permalink
feat(hex,grid): add round() and pointToCube() functions and pointToHe…
Browse files Browse the repository at this point in the history
…x() method to Grid

round() converts float axial coordinates to integer cube coordinates. pointToCube() converts a point
to cube coordinates. pointToHex() converts a point (e.g. a pixel) to a hex in a grid.
  • Loading branch information
flauwekeul committed Apr 22, 2021
1 parent 2c4f3c5 commit b302c08
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 5 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ These methods exist in v3 and they need to be considered for v4.
- [x] ~~cube~~ considered obsolete
- [x] ~~cubeToCartesian (alias: toCartesian)~~ replaced with `hexToOffset()`
- [x] equals
- [ ] from (convert anything(?) to a hex)
- [x] fromPoint: `pointToCube()`
- [x] height
- [x] isFlat
- [x] isPointy
- [ ] lerp
- [ ] nudge
- [ ] round
- [x] round
- [ ] ? set
- [ ] ? subtract
- [ ] thirdCoordinate
Expand All @@ -169,7 +169,7 @@ These methods exist in v3 and they need to be considered for v4.
- [ ] grid functions (apply to multiple hexes):
- [ ] ? distance
- [x] hexToPoint
- [ ] pointToHex
- [x] pointToHex
- [x] get
- [ ] hexesBetween
- [ ] hexesInRange
Expand Down
14 changes: 14 additions & 0 deletions src/grid/grid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ test('can be iterated', () => {
}
})

describe('pointToHex()', () => {
test('converts a point to a hex', () => {
const grid = new Grid(hexPrototype)
const hex = {} as Hex
const getHex = jest.spyOn(grid, 'getHex').mockReturnValue(hex)
const point = { x: 1, y: 2 }

const result = grid.pointToHex(point)

expect(result).toBe(hex)
expect(getHex).toBeCalledWith({ q: -0, r: 1, s: -1 })
})
})

describe('getHex()', () => {
test('returns a hex from the store when present in the store', () => {
const coordinates = { q: 1, r: 2 }
Expand Down
6 changes: 5 additions & 1 deletion src/grid/grid.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createHex, Hex, HexCoordinates } from '../hex'
import { createHex, Hex, HexCoordinates, Point, pointToCube } from '../hex'
import { flatTraverse } from './functions'
import { Callback, Traverser } from './types'

Expand Down Expand Up @@ -57,6 +57,10 @@ export class Grid<T extends Hex> {
}
}

pointToHex(point: Point): T {
return this.getHex(pointToCube(this.hexPrototype, point))
}

each(callback: Callback<T, void>) {
const each: GetHexState<T> = (currentGrid) => {
const prevHexState = this._getPrevHexState(currentGrid)
Expand Down
2 changes: 2 additions & 0 deletions src/hex/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ export * from './isFlat'
export * from './isHex'
export * from './isPointy'
export * from './offsetToAxial'
export * from './pointToCube'
export * from './round'
export * from './toString'
export * from './width'
50 changes: 50 additions & 0 deletions src/hex/functions/pointToCube.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createHexPrototype } from './createHexPrototype'
import { pointToCube } from './pointToCube'

describe('pointy hex', () => {
test('converts a point to cube coordinates', () => {
const pointyHexPrototype = createHexPrototype({
orientation: 'pointy',
dimensions: { xRadius: 50, yRadius: 30 },
origin: { x: -30, y: -30 },
})

// test points close to each side of all edges of hex with coordinates { q: 3, r: 4 }
expect(pointToCube(pointyHexPrototype, { x: 440, y: 185 })).toMatchObject({ q: 3, r: 3 })
expect(pointToCube(pointyHexPrototype, { x: 440, y: 190 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 485, y: 185 })).toMatchObject({ q: 4, r: 3 })
expect(pointToCube(pointyHexPrototype, { x: 485, y: 190 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 505, y: 210 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 510, y: 210 })).toMatchObject({ q: 4, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 440, y: 230 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 440, y: 235 })).toMatchObject({ q: 2, r: 5 })
expect(pointToCube(pointyHexPrototype, { x: 485, y: 230 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 485, y: 235 })).toMatchObject({ q: 3, r: 5 })
expect(pointToCube(pointyHexPrototype, { x: 415, y: 210 })).toMatchObject({ q: 2, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 420, y: 210 })).toMatchObject({ q: 3, r: 4 })
})
})

describe('flat hex', () => {
test('converts a point to cube coordinates', () => {
const pointyHexPrototype = createHexPrototype({
orientation: 'flat',
dimensions: { xRadius: 50, yRadius: 30 },
origin: { x: -30, y: -30 },
})

// test points close to each side of all edges of hex with coordinates { q: 3, r: 4 }
expect(pointToCube(pointyHexPrototype, { x: 255, y: 285 })).toMatchObject({ q: 3, r: 3 })
expect(pointToCube(pointyHexPrototype, { x: 255, y: 290 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 290, y: 300 })).toMatchObject({ q: 4, r: 3 })
expect(pointToCube(pointyHexPrototype, { x: 290, y: 305 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 290, y: 325 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 290, y: 335 })).toMatchObject({ q: 4, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 255, y: 340 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 255, y: 345 })).toMatchObject({ q: 3, r: 5 })
expect(pointToCube(pointyHexPrototype, { x: 220, y: 325 })).toMatchObject({ q: 3, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 220, y: 335 })).toMatchObject({ q: 2, r: 5 })
expect(pointToCube(pointyHexPrototype, { x: 220, y: 300 })).toMatchObject({ q: 2, r: 4 })
expect(pointToCube(pointyHexPrototype, { x: 220, y: 305 })).toMatchObject({ q: 3, r: 4 })
})
})
18 changes: 18 additions & 0 deletions src/hex/functions/pointToCube.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { HexPrototype, Point } from '../types'
import { round } from './round'

// inspired by https://github.com/gojuno/hexgrid-py
// and simplified by https://www.symbolab.com/solver/simplify-calculator/simplify
export const pointToCube = (
{ dimensions: { xRadius, yRadius }, origin, isPointy }: Pick<HexPrototype, 'dimensions' | 'origin' | 'isPointy'>,
{ x, y }: Point,
) => {
x += origin.x
y += origin.y

if (isPointy) {
return round({ q: (Math.sqrt(3) * x) / (3 * xRadius) - y / (3 * yRadius), r: (2 / 3) * (y / yRadius) })
}

return round({ q: (2 / 3) * (x / xRadius), r: (Math.sqrt(3) * y) / (3 * yRadius) - x / (3 * xRadius) })
}
7 changes: 7 additions & 0 deletions src/hex/functions/round.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { round } from './round'

test('rounds the passed axial or cube coordinates', () => {
expect(round({ q: 1.5, r: 1.1 })).toEqual({ q: 2, r: 1, s: -3 })
expect(round({ q: 1.1, r: 1.5 })).toEqual({ q: 1, r: 2, s: -3 })
expect(round({ q: 1.1, r: 1.1 })).toEqual({ q: 1, r: 1, s: -2 })
})
20 changes: 20 additions & 0 deletions src/hex/functions/round.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CubeCoordinates, PartialCubeCoordinates } from '../types'

export const round = ({ q, r, s = -q - r }: PartialCubeCoordinates): CubeCoordinates => {
let roundedQ = Math.round(q)
let roundedR = Math.round(r)
let roundedS = Math.round(s)
const diffQ = Math.abs(q - roundedQ)
const diffR = Math.abs(r - roundedR)
const diffS = Math.abs(s - roundedS)

if (diffQ > diffR && diffQ > diffS) {
roundedQ = -roundedR - roundedS
} else if (diffR > diffS) {
roundedR = -roundedQ - roundedS
} else {
roundedS = -roundedQ - roundedR
}

return { q: roundedQ, r: roundedR, s: roundedS }
}
6 changes: 5 additions & 1 deletion src/hex/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export interface CubeCoordinates extends AxialCoordinates {
s: number
}

export type HexCoordinates = (AxialCoordinates & { s?: number }) | OffsetCoordinates
export interface PartialCubeCoordinates extends AxialCoordinates {
s?: number
}

export type HexCoordinates = PartialCubeCoordinates | OffsetCoordinates

export interface Ellipse {
xRadius: number
Expand Down

0 comments on commit b302c08

Please sign in to comment.