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

  1. import DataScanner from "../util/scanner";
  2. export enum TileType {
  3. WALKABLE,
  4. OBSTRUCTED,
  5. CLIFF,
  6. }
  7. export enum Corner {
  8. TOP_LEFT,
  9. TOP_RIGHT,
  10. BOTTOM_RIGHT,
  11. BOTTOM_LEFT,
  12. }
  13. export type TileAltitudes = Record<Corner, number>;
  14. export type Tile = {
  15. altitude: TileAltitudes;
  16. ty: TileType;
  17. };
  18. export type GAT = {
  19. height: number;
  20. tiles: Tile[];
  21. width: number;
  22. };
  23. export class ParseError extends Error {
  24. constructor(message: string) {
  25. super(message);
  26. }
  27. }
  28. const HEADER_MAGIC = 0x54415247;
  29. const HEADER_SIZE_BYTES = 14;
  30. const SUPPORTED_VERSION = "1.2";
  31. const TILE_SIZE_BYTES = 20;
  32. const SCALING_FACTOR = -1 / 5;
  33. const TERRAIN_TYPE_TO_TILE_TYPE: Record<number, TileType> = {
  34. 0: TileType.WALKABLE,
  35. 1: TileType.OBSTRUCTED,
  36. 5: TileType.CLIFF,
  37. };
  38. export const parseGAT = (data: ArrayBuffer): GAT => {
  39. const scanner = new DataScanner(data);
  40. const magic = scanner.uint32();
  41. if (magic !== HEADER_MAGIC) {
  42. throw new ParseError("invalid magic number");
  43. }
  44. const version = `${scanner.uint8()}.${scanner.uint8()}`;
  45. if (version !== SUPPORTED_VERSION) {
  46. throw new ParseError(`unsupported file version ${version}`);
  47. }
  48. const width = scanner.uint32();
  49. const height = scanner.uint32();
  50. const expectedSize = HEADER_SIZE_BYTES + width * height * TILE_SIZE_BYTES;
  51. if (data.byteLength !== expectedSize) {
  52. throw new ParseError(`unexpected size of data`);
  53. }
  54. const tiles = [];
  55. for (let i = 0; i < width * height; ++i) {
  56. tiles[i] = parseTile(scanner);
  57. }
  58. return {
  59. height,
  60. tiles,
  61. width,
  62. };
  63. };
  64. const parseTile = (scanner: DataScanner): Tile => {
  65. const bottomLeft = scanner.float32();
  66. const bottomRight = scanner.float32();
  67. const topLeft = scanner.float32();
  68. const topRight = scanner.float32();
  69. const terrainType = scanner.uint32() & 0xffff;
  70. if (typeof TERRAIN_TYPE_TO_TILE_TYPE[terrainType] === "undefined") {
  71. throw new Error(`unknown terrain type 0x${terrainType.toString(16)}`);
  72. }
  73. return {
  74. ty: TERRAIN_TYPE_TO_TILE_TYPE[terrainType],
  75. altitude: {
  76. [Corner.TOP_LEFT]: topLeft * SCALING_FACTOR,
  77. [Corner.TOP_RIGHT]: topRight * SCALING_FACTOR,
  78. [Corner.BOTTOM_RIGHT]: bottomRight * SCALING_FACTOR,
  79. [Corner.BOTTOM_LEFT]: bottomLeft * SCALING_FACTOR,
  80. },
  81. };
  82. };