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.
 
 
 

100 lines
2.2 KiB

import DataScanner from "../util/scanner";
export enum TileType {
WALKABLE,
OBSTRUCTED,
CLIFF,
}
export enum Corner {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT,
BOTTOM_LEFT,
}
export type TileAltitudes = Record<Corner, number>;
export type Tile = {
altitude: TileAltitudes;
ty: TileType;
};
export type GAT = {
height: number;
tiles: Tile[];
width: number;
};
export class ParseError extends Error {
constructor(message: string) {
super(message);
}
}
const HEADER_MAGIC = 0x54415247;
const HEADER_SIZE_BYTES = 14;
const SUPPORTED_VERSION = "1.2";
const TILE_SIZE_BYTES = 20;
const SCALING_FACTOR = -1 / 5;
const TERRAIN_TYPE_TO_TILE_TYPE: Record<number, TileType> = {
0: TileType.WALKABLE,
1: TileType.OBSTRUCTED,
5: TileType.CLIFF,
};
export const parseGAT = (data: ArrayBuffer): GAT => {
const scanner = new DataScanner(data);
const magic = scanner.uint32();
if (magic !== HEADER_MAGIC) {
throw new ParseError("invalid magic number");
}
const version = `${scanner.uint8()}.${scanner.uint8()}`;
if (version !== SUPPORTED_VERSION) {
throw new ParseError(`unsupported file version ${version}`);
}
const width = scanner.uint32();
const height = scanner.uint32();
const expectedSize = HEADER_SIZE_BYTES + width * height * TILE_SIZE_BYTES;
if (data.byteLength !== expectedSize) {
throw new ParseError(`unexpected size of data`);
}
const tiles = [];
for (let i = 0; i < width * height; ++i) {
tiles[i] = parseTile(scanner);
}
return {
height,
tiles,
width,
};
};
const parseTile = (scanner: DataScanner): Tile => {
const bottomLeft = scanner.float32();
const bottomRight = scanner.float32();
const topLeft = scanner.float32();
const topRight = scanner.float32();
const terrainType = scanner.uint32() & 0xffff;
if (typeof TERRAIN_TYPE_TO_TILE_TYPE[terrainType] === "undefined") {
throw new Error(`unknown terrain type 0x${terrainType.toString(16)}`);
}
return {
ty: TERRAIN_TYPE_TO_TILE_TYPE[terrainType],
altitude: {
[Corner.TOP_LEFT]: topLeft * SCALING_FACTOR,
[Corner.TOP_RIGHT]: topRight * SCALING_FACTOR,
[Corner.BOTTOM_RIGHT]: bottomRight * SCALING_FACTOR,
[Corner.BOTTOM_LEFT]: bottomLeft * SCALING_FACTOR,
},
};
};