You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
hexmap/common/src/state/Coordinates.ts

137 lines
6.7 KiB

import {HexagonOrientation, HexLayout, LineParity} from "./HexMap";
/** Staggered (storage) coordinates for accessing cell storage. */
export interface StorageCoordinates {
/** The index of the line within the map. */
readonly line: number
/** The index of the cell within the line. */
readonly cell: number
}
/** Translates storage coordinates to a key unique among all cells in the map. */
export function storageCoordinatesToKey({line, cell}: StorageCoordinates): string {
return `${line},${cell}`
}
/**
* The full set of coordinates on screen for a hexagon.
* The axis changes depending on the orientation.
* For POINTY_TOP mode, the flat axis is the X axis, and the pointy axis is the Y axis.
* For FLAT_TOP mode, the flat axis is the Y axis, and the pointy axis is the X axis.
* The vertices of a hexagon will always be at these pairs, in this order, with the axes set as above:
* pointyStart, flatCenter
* pointyFirstThird, flatStart
* pointyLastThird, flatStart
* pointyEnd, flatCenter
* pointyLastThird, flatEnd
* pointyFirstThird, flatEnd
*/
export interface RenderCoordinates {
/** The orientation of the hexagons, for determining which axes to use. */
readonly orientation: HexagonOrientation,
/** The beginning of the pointy axis - where the first pointy vertex is, on the axis that runs through the pointy vertices. */
readonly pointyStart: number,
/** The end of the pointy axis - where the last pointy vertex is, on the axis that runs through the pointy vertices. */
readonly pointyEnd: number,
/** The first "third" (actually a quarter) of the pointy axis - where the first vertex of each flat side is, on the axis that runs through the pointy vertices. */
readonly pointyFirstThird: number,
/** The last "third" (actually a quarter) of the pointy axis - where the last vertex of each flat side is, on the axis that runs through the pointy vertices. */
readonly pointyLastThird: number,
/** The start of the flat axis - where the first flat side is, on the axis that runs through the flat sides. */
readonly flatStart: number,
/** The center of the flat axis - where the two pointy vertices are, on the axis that runs through the flat sides. */
readonly flatCenter: number,
/** The end of the flat axis - where the second flat side is, on the axis that runs through the flat sides. */
readonly flatEnd: number,
}
/** The offsets for rendering a hexagonal map. */
export interface RenderOffsets {
/** The distance from the left the first x coordinate should be. */
readonly left: number
/** The distance from the top the first y coordinate should be. */
readonly top: number
/** How big each hexagon should be. The "radius" from the center to any vertex. */
readonly size: number
/** The way the hex map should be displayed. Usually the same as the origin map. */
readonly layout: HexLayout
}
export interface RenderSize {
readonly width: number
readonly height: number
}
// Heavily based on https://www.redblobgames.com/grids/hexagons/
const POINTY_AXIS_FACTOR = 2;
const FLAT_AXIS_FACTOR = Math.sqrt(3);
export function storageCoordinatesToRenderCoordinates({line, cell}: StorageCoordinates, renderOffsets: RenderOffsets): RenderCoordinates {
const {orientation, indentedLines} = renderOffsets.layout;
const flatLength = renderOffsets.size * FLAT_AXIS_FACTOR;
const pointyLength = renderOffsets.size * POINTY_AXIS_FACTOR;
const flatStorageCoordinate = cell;
const pointyStorageCoordinate = line;
const lineParity = (pointyStorageCoordinate % 2) === 0 ? LineParity.EVEN : LineParity.ODD;
const isIndented = lineParity === indentedLines;
const flatOffset = orientation === HexagonOrientation.FLAT_TOP ? renderOffsets.top : renderOffsets.left;
const pointyOffset = orientation === HexagonOrientation.POINTY_TOP ? renderOffsets.top : renderOffsets.left;
const pointyStart = pointyOffset + pointyStorageCoordinate * pointyLength * 3 / 4;
const pointyFirstThird = pointyStart + pointyLength / 4
const pointyLastThird = pointyStart + pointyLength * 3 / 4
const pointyEnd = pointyStart + pointyLength;
const flatStart = flatOffset + flatLength * ((isIndented ? 0.5 : 0) + flatStorageCoordinate);
const flatCenter = flatStart + flatLength / 2;
const flatEnd = flatStart + flatLength;
return {
orientation,
pointyStart,
pointyFirstThird,
pointyLastThird,
pointyEnd,
flatStart,
flatCenter,
flatEnd
}
}
export function sizeFromLinesAndCells({offsets, lines, cells, rightMargin = 0, bottomMargin = 0}: {offsets: RenderOffsets, lines: number, cells: number, rightMargin?: number, bottomMargin?: number}): RenderSize {
const {
top: topMargin,
left: leftMargin,
layout: {
orientation,
indentedLines
}
} = offsets;
const flatMargins = orientation === HexagonOrientation.FLAT_TOP ? topMargin + bottomMargin : leftMargin + rightMargin;
const pointyMargins = orientation === HexagonOrientation.POINTY_TOP ? topMargin + bottomMargin : leftMargin + rightMargin;
const flatCellLength = offsets.size * FLAT_AXIS_FACTOR;
const pointyCellLength = offsets.size * POINTY_AXIS_FACTOR;
const hasIndents = lines > 1 || indentedLines === LineParity.EVEN
// Every cell will be 3/4 of a cell length apart in the pointy direction;
// however, the last line is still one full pointy cell long.
const pointyLength = pointyMargins + ((lines - 1) * pointyCellLength * 3 / 4) + pointyCellLength;
// Every line will be one full cell length apart in the flat direction;
// however, if there are indents, another half cell is needed to accommodate them.
const flatLength = flatMargins + cells * flatCellLength + (hasIndents ? flatCellLength / 2 : 0);
return orientation === HexagonOrientation.FLAT_TOP ? { width: pointyLength, height: flatLength } : { width: flatLength, height: pointyLength }
}
export function renderCoordinatesToPolygonPoints(coords: RenderCoordinates): string {
if (coords.orientation === HexagonOrientation.FLAT_TOP) {
return `${coords.pointyStart},${coords.flatCenter} ${coords.pointyFirstThird},${coords.flatStart} ${coords.pointyLastThird},${coords.flatStart} ${coords.pointyEnd},${coords.flatCenter} ${coords.pointyLastThird},${coords.flatEnd} ${coords.pointyFirstThird},${coords.flatEnd}`
} else /* if (coords.orientation === HexagonOrientation.POINTY_TOP) */ {
return `${coords.flatCenter},${coords.pointyStart} ${coords.flatStart},${coords.pointyFirstThird} ${coords.flatStart},${coords.pointyLastThird} ${coords.flatCenter},${coords.pointyEnd} ${coords.flatEnd},${coords.pointyLastThird} ${coords.flatEnd},${coords.pointyFirstThird}`
}
}