From 4c6005e64d340e9abfc85bd0421f8e7f513caa8e Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Mon, 14 Mar 2022 08:52:27 +0800 Subject: [PATCH] Refactor code Organize code into services. --- src/backend/services/Biome.service.ts | 134 +++++++ src/backend/services/Color.service.ts | 9 + src/backend/services/Projection.service.ts | 16 +- src/backend/services/Tile.service.ts | 281 ++++++++++++++ src/components/GenerateMapForm/index.tsx | 1 - src/pages/api/generate/index.ts | 423 +++------------------ src/utils/types.ts | 10 + 7 files changed, 497 insertions(+), 377 deletions(-) create mode 100644 src/backend/services/Biome.service.ts create mode 100644 src/backend/services/Color.service.ts create mode 100644 src/backend/services/Tile.service.ts diff --git a/src/backend/services/Biome.service.ts b/src/backend/services/Biome.service.ts new file mode 100644 index 0000000..dd8eab0 --- /dev/null +++ b/src/backend/services/Biome.service.ts @@ -0,0 +1,134 @@ +import ColorService, {ColorServiceImpl} from './Color.service'; +import {LandType} from '../../utils/types'; + +export default interface BiomeService { + checkBiomeLandType(x: number, y: number, width: number): LandType + isMountainBiome(x: number, y: number, width: number): boolean + isHillBiome(x: number, y: number, width: number): boolean + isWoodedBiome(x: number, y: number, width: number): boolean +} + +export class BiomeServiceImpl implements BiomeService { + private readonly colorService: ColorService + + constructor(private readonly biomePngData: Buffer) { + this.colorService = new ColorServiceImpl() + } + + checkBiomeLandType(x: number, y: number, width: number): LandType { + const dataIndex = ((y * width) + x) * 4; + switch (this.colorService.rgb( + this.biomePngData[dataIndex], + this.biomePngData[dataIndex + 1], + this.biomePngData[dataIndex + 2], + )) { + case 0xfa9418: + case 0xffbc40: + return LandType.DESERT; + case 0x606060: + case 0xd25f12: + case 0xd94515: + case 0xca8c65: + return LandType.MESA; + case 0x056621: + case 0x0b6659: + case 0x537b09: + case 0x2c4205: + case 0x628b17: + case 0x507050: + case 0x2d8e49: + case 0x8ab33f: + case 0x687942: + return LandType.FOREST; + case 0x596651: + case 0x454f3e: + case 0x338e81: + // return LandType.MUD; + // case 0xfade55: + // return LandType.BEACH; + case 0xffffff: + case 0xa0a0a0: + case 0xfaf0c0: + case 0x597d72: + case 0x202070: + case 0x202038: + case 0x404090: + case 0x163933: + return LandType.ICE; + case 0xfade55: + case 0x8db360: + case 0x07f9b2: + case 0xbdb25f: + case 0xa79d64: + case 0xb5db88: + case 0x000070: + case 0x0000ff: + case 0x0000ac: + case 0x000090: + case 0x000050: + case 0x000040: + default: + break; + } + + return LandType.PLAINS; + } + + isMountainBiome(x: number, y: number, width: number): boolean { + const dataIndex = ((y * width) + x) * 4; + switch (this.colorService.rgb( + (this.biomePngData[dataIndex]), + (this.biomePngData[dataIndex + 1]), + (this.biomePngData[dataIndex + 2]), + )) { + case 0x606060: + case 0xa0a0a0: + case 0x507050: + case 0x338e81: + case 0x597d72: + return true + default: + break + } + + return false + } + + isHillBiome(x: number, y: number, width: number): boolean { + const dataIndex = ((y * width) + x) * 4; + switch (this.colorService.rgb( + (this.biomePngData[dataIndex]), + (this.biomePngData[dataIndex + 1]), + (this.biomePngData[dataIndex + 2]), + )) { + case 0xd25f12: + case 0x163933: + case 0x2c4205: + case 0x454f3e: + case 0x687942: + return true + default: + break + } + + return false + } + + isWoodedBiome(x: number, y: number, width: number): boolean { + const dataIndex = ((y * width) + x) * 4; + switch (this.colorService.rgb( + (this.biomePngData[dataIndex]), + (this.biomePngData[dataIndex + 1]), + (this.biomePngData[dataIndex + 2]), + )) { + case 0x056621: + case 0x537b09: + case 0x596651: + case 0x2d8e49: + return true; + default: + break + } + return false; + } +} diff --git a/src/backend/services/Color.service.ts b/src/backend/services/Color.service.ts new file mode 100644 index 0000000..0b31bbe --- /dev/null +++ b/src/backend/services/Color.service.ts @@ -0,0 +1,9 @@ +export default interface ColorService { + rgb(r: number, g: number, b: number): number +} + +export class ColorServiceImpl implements ColorService { + rgb(r: number, g: number, b: number): number { + return (r << 16) + (g << 8) + b + } +} diff --git a/src/backend/services/Projection.service.ts b/src/backend/services/Projection.service.ts index f2461b2..2c7015c 100644 --- a/src/backend/services/Projection.service.ts +++ b/src/backend/services/Projection.service.ts @@ -3,11 +3,14 @@ import {Projection} from '../../utils/types'; import * as d3geo from 'd3-geo'; type ProjectionData = { - fn: Function, + fn: (...args: unknown[]) => d3geo.GeoProjection, bounds: (a: [number, number]) => [number, number], } -export default interface ProjectionService {} +export default interface ProjectionService { + project(equiImage: PNG, projection: Projection): PNG | null +} + export class ProjectionServiceImpl implements ProjectionService { private readonly projections: Record = { [Projection.EQUIRECTANGULAR]: { @@ -26,20 +29,21 @@ export class ProjectionServiceImpl implements ProjectionService { fn: d3geo.geoConicConformal, bounds: (a) => a, }, - } as const private readonly referenceWidth = 960 as const; - project(equiImage: PNG, projection: Projection) { + project(equiImage: PNG, projection: Projection): PNG | null { if (projection === Projection.EQUIRECTANGULAR) { return equiImage; } const { [projection]: currentProjectionData } = this.projections - const { invert } = currentProjectionData.fn(); + const baseProjection = currentProjectionData.fn(); + const modifiedProjection = baseProjection.center([0, 0]); + const { invert } = modifiedProjection; if (!invert) { - return undefined; + return null; } const { width: sx, height: sy, data: sourceData } = equiImage; diff --git a/src/backend/services/Tile.service.ts b/src/backend/services/Tile.service.ts new file mode 100644 index 0000000..24c1d0a --- /dev/null +++ b/src/backend/services/Tile.service.ts @@ -0,0 +1,281 @@ +import BiomeService, {BiomeServiceImpl} from './Biome.service'; +import {LandType, WaterType} from '../../utils/types'; + +export type NeighborData = { + nw: boolean, + n: boolean, + ne: boolean, + w: boolean, + i: number, + e: boolean, + sw: boolean, + s: boolean, + se: boolean, +} + +export default interface TileService { + determineBackgroundTileType(x: number, y: number, width: number, height: number): number + determineForegroundTileType(tileType: number, x: number, y: number, dx: number): number; + determineWaterBackgroundTileType(x: number, y: number, dx: number): number; +} + +export class TileServiceImpl implements TileService { + private index(x: number, y: number, width: number) { + return ((y * width) + x) * 4; + } + + private readonly landThreshold = 0x80 + + private readonly SHALLOW_WATER_THRESHOLD = 0xE0 + + private checkNeighboringLands(waterMaskPngData: Buffer, x: number, y: number, width: number, height: number): NeighborData { + const ny = y === 0 ? 0 : y - 1; + const wx = (x + width - 1) % width; + const ex = (x + 1) % width ; + const sy = y === height - 1 ? height - 1 : y + 1; + + return { + nw: waterMaskPngData[this.index(wx, ny, width)] < this.landThreshold, + n: waterMaskPngData[this.index(x, ny, width)] < this.landThreshold, + ne: waterMaskPngData[this.index(ex, ny, width)] < this.landThreshold, + w: waterMaskPngData[this.index(wx, y, width)] < this.landThreshold, + i: waterMaskPngData[this.index(x, y, width)], + e: waterMaskPngData[this.index(ex, y, width)] < this.landThreshold, + sw: waterMaskPngData[this.index(wx, sy, width)] < this.landThreshold, + s: waterMaskPngData[this.index(x, sy, width)] < this.landThreshold, + se: waterMaskPngData[this.index(ex, sy, width)] < this.landThreshold, + }; + } + + private determineCoastalBackgroundTileType(landFlags: NeighborData) { + let cursor = 0; + if (landFlags.n) { + cursor += 12; // 12 + if (landFlags.e) { + cursor += 12; // 24 + if (landFlags.s) { + cursor += 14; // 38 + if (landFlags.w) { + cursor -= 10; // 28 + } + } else if (landFlags.w) { + cursor += 16; // 40 + } else if (landFlags.sw) { + cursor += 12; // 36 + } + } else if (landFlags.w) { + cursor += 15; // 27 + if (landFlags.s) { + cursor += 12; // 39 + } else if (landFlags.se) { + cursor += 10; // 37 + } + } else if (landFlags.s) { + cursor += 31; // 43 + } else if (landFlags.se) { + cursor += 8; // 20 + if (landFlags.sw) { + cursor += 12; // 32 + } + } else if (landFlags.sw) { + cursor += 11; // 23 + } + } else if (landFlags.s) { + cursor += 13; // 13 + if (landFlags.e) { + cursor += 12; // 25 + if (landFlags.w) { + cursor += 16; // 41 + } else if (landFlags.nw) { + cursor += 10; // 35 + } + } else if (landFlags.w) { + cursor += 13; // 26 + if (landFlags.ne) { + cursor += 8; // 34 + } + } else if (landFlags.ne) { + cursor += 8; // 21 + if (landFlags.nw) { + cursor += 20; // 33 + } + } else if (landFlags.nw) { + cursor += 9; // 22 + } + } else if (landFlags.w) { + cursor += 14; // 14 + if (landFlags.e) { + cursor += 28; // 42 + } else if (landFlags.se) { + cursor += 4; // 18 + if (landFlags.ne) { + cursor += 13; // 31 + } + } else if (landFlags.ne) { + cursor += 5; // 19 + } + } else if (landFlags.e) { + cursor += 15; // 15 + if (landFlags.nw) { + cursor += 1 // 16 + if (landFlags.sw) { + cursor += 14; // 30 + } + } else if (landFlags.sw) { + cursor += 2; // 17 + } + } else if (landFlags.ne) { + cursor += 2; // 2 + if (landFlags.nw) { + cursor += 4; // 6 + if (landFlags.sw) { + cursor += 42; // 48 + if (landFlags.se) { + cursor -= 4; // 44 + } + } else if (landFlags.se) { + cursor += 39; // 45 + } + } else if (landFlags.se) { + cursor += 7; // 9 + if (landFlags.sw) { + cursor += 37; // 46 + } + } else if (landFlags.sw) { + cursor += 8; // 10 + } + } else if (landFlags.nw) { + cursor += 3; // 3 + if (landFlags.sw) { + cursor += 5; // 8 + if (landFlags.se) { + cursor += 39; // 47 + } + } else if (landFlags.se) { + cursor += 8; // 11 + } + } else if (landFlags.sw) { + cursor += 4; // 4 + if (landFlags.se) { + cursor += 3; // 7 + } + } else if (landFlags.se) { + cursor += 5; // 5 + } + + return cursor; + } + + private determineBaseBackgroundTileType(waterMaskPngData: Buffer, x: number, y: number, width: number, height: number): number { + const landFlags = this.checkNeighboringLands(waterMaskPngData, x, y, width, height); + + const islandThreshold = 0xE0; + if (landFlags.i < this.landThreshold) { + return 1; // 1 + } + + if ( + landFlags.i < islandThreshold + && !( + landFlags.nw + || landFlags.n + || landFlags.ne + || landFlags.w + || landFlags.e + || landFlags.sw + || landFlags.s + || landFlags.se + ) + ) { + return 29; // 29 + } + + return this.determineCoastalBackgroundTileType(landFlags) + } + + private readonly biomeService: BiomeService + + private getMountainForegroundTileType(landType: LandType): number { + switch (landType) { + case LandType.DESERT: + case LandType.MESA: + return 796; + case LandType.ICE: + return 799; + case LandType.FOREST: + case LandType.PLAINS: + default: + return 797; + } + } + + private getHillForegroundTileType(landType: LandType): number { + if (landType === LandType.ICE) { + return 903 + } + return 901 + } + + private getWoodedForegroundTileType(landType: LandType): number { + if (landType === LandType.ICE) { + return 906 + } + return 905 + } + + constructor( + biomePngData: Buffer, + private readonly elevaionPngData: Buffer, + private readonly waterMaskPngData: Buffer, + private readonly bathymetryPngData: Buffer, + ) { + this.biomeService = new BiomeServiceImpl(biomePngData) + } + + determineBackgroundTileType(x: number, y: number, width: number, height: number): number { + const landType = this.biomeService.checkBiomeLandType(x, y, width); + const tileType = this.determineBaseBackgroundTileType(this.waterMaskPngData, x, y, width, height); + return ((landType * 60) + tileType); + } + + private determineLandForegroundTileType(x: number, y: number, dx: number): number { + const landType = this.biomeService.checkBiomeLandType(x, y, dx); + + const i = this.index(x, y, dx); + const isMountain = this.elevaionPngData[i] >= 0x40 || this.biomeService.isMountainBiome( + x, + y, + dx + ); + if (isMountain) { + return this.getMountainForegroundTileType(landType); + } + + if (this.biomeService.isHillBiome(x, y, dx)) { + return this.getHillForegroundTileType(landType) + } + + if (this.biomeService.isWoodedBiome(x, y, dx)) { + return this.getWoodedForegroundTileType(landType); + } + + return 0; + } + + private determineWaterForegroundTileType(x: number, y: number, dx: number): number { + return 0; + } + + determineWaterBackgroundTileType(x: number, y: number, dx: number): number { + const i = this.index(x, y, dx); + const isShallowWater = this.bathymetryPngData[i] >= this.SHALLOW_WATER_THRESHOLD; + return isShallowWater ? WaterType.LIGHT : WaterType.DEFAULT; + } + + determineForegroundTileType(tileType: number, x: number, y: number, dx: number): number { + if (tileType % 60 === 1) { + return this.determineLandForegroundTileType(x, y, dx); + } + return this.determineWaterForegroundTileType(x, y, dx); + } +} diff --git a/src/components/GenerateMapForm/index.tsx b/src/components/GenerateMapForm/index.tsx index 867895d..c818853 100644 --- a/src/components/GenerateMapForm/index.tsx +++ b/src/components/GenerateMapForm/index.tsx @@ -4,7 +4,6 @@ import {Checkbox} from '../Checkbox'; import {NumericInput} from '../NumericInput'; import styles from '../../styles/components/GenerateMapForm/index.module.css'; import {ActionButton} from '../ActionButton'; -import {MapType} from '../../utils/types'; export const GenerateMapForm: VFC = () => { return ( diff --git a/src/pages/api/generate/index.ts b/src/pages/api/generate/index.ts index 186576d..086ca2e 100644 --- a/src/pages/api/generate/index.ts +++ b/src/pages/api/generate/index.ts @@ -1,11 +1,12 @@ import {NextApiHandler} from 'next'; import * as fs from 'fs/promises'; import {PNG} from 'pngjs'; -import {MapType, Projection, WaterType, WorldData} from '../../../utils/types'; +import {MapType, Projection, WorldData} from '../../../utils/types'; import {Stats} from 'fs'; import sharp from 'sharp'; -import {ProjectionServiceImpl} from '../../../backend/services/Projection.service'; +import ProjectionService, {ProjectionServiceImpl} from '../../../backend/services/Projection.service'; import {FileSystemServiceImpl} from '../../../backend/services/FileSystem.service'; +import TileService, {TileServiceImpl} from '../../../backend/services/Tile.service'; const generateProjectedBaseData = async (t: MapType, p: Projection) => { const destPath = `public/generated/base/${p}-${t}.png`; @@ -25,7 +26,7 @@ const generateProjectedBaseData = async (t: MapType, p: Projection) => { if (shouldGenerateFile) { const inputBuffer = await fs.readFile(`src/assets/data/000/${t}.png`) const inputPng = PNG.sync.read(inputBuffer); - const projectionService = new ProjectionServiceImpl() + const projectionService: ProjectionService = new ProjectionServiceImpl() const outputPng = projectionService.project(inputPng, p) as PNG; const outputBuffer = PNG.sync.write(outputPng); await fs.writeFile(destPath, outputBuffer); @@ -64,307 +65,17 @@ const resizeBaseData = async (t: MapType, p: Projection, size: number, isWidthDi .toBuffer(); await fs.writeFile(destPath, buffer); - return destPath.replace('public/', '/'); -} - -enum LandType { - MESA = 0, - DESERT = 1, - FOREST = 2, - PLAINS = 3, - ICE = 4, - BEACH = 5, - MUD = 6, -} - -const rgb = (r: number, g: number, b: number) => { - return (r << 16) + (g << 8) + b -} - -const checkBiomeLandType = (biomePngData: Buffer, x: number, y: number, width: number) => { - const dataIndex = ((y * width) + x) * 4; - switch (rgb( - biomePngData[dataIndex], - biomePngData[dataIndex + 1], - biomePngData[dataIndex + 2], - )) { - case 0xfa9418: - case 0xffbc40: - return LandType.DESERT; - case 0x606060: - case 0xd25f12: - case 0xd94515: - case 0xca8c65: - return LandType.MESA; - case 0x056621: - case 0x0b6659: - case 0x537b09: - case 0x2c4205: - case 0x628b17: - case 0x507050: - case 0x2d8e49: - case 0x8ab33f: - case 0x687942: - return LandType.FOREST; - case 0x596651: - case 0x454f3e: - case 0x338e81: - // return LandType.MUD; - // case 0xfade55: - // return LandType.BEACH; - case 0xffffff: - case 0xa0a0a0: - case 0xfaf0c0: - case 0x597d72: - case 0x202070: - case 0x202038: - case 0x404090: - case 0x163933: - return LandType.ICE; - case 0xfade55: - case 0x8db360: - case 0x07f9b2: - case 0xbdb25f: - case 0xa79d64: - case 0xb5db88: - case 0x000070: - case 0x0000ff: - case 0x0000ac: - case 0x000090: - case 0x000050: - case 0x000040: - default: - break; - } - - return LandType.PLAINS; -} - -const isMountainBiome = (biomePngData: Buffer, x: number, y: number, width: number) => { - const dataIndex = ((y * width) + x) * 4; - switch (rgb( - (biomePngData[dataIndex]), - (biomePngData[dataIndex + 1]), - (biomePngData[dataIndex + 2]), - )) { - case 0x606060: - case 0xa0a0a0: - case 0x507050: - case 0x338e81: - case 0x597d72: - return true - default: - break - } - - return false -} - -const isHillBiome = (biomePngData: Buffer, x: number, y: number, width: number) => { - const dataIndex = ((y * width) + x) * 4; - switch (rgb( - (biomePngData[dataIndex]), - (biomePngData[dataIndex + 1]), - (biomePngData[dataIndex + 2]), - )) { - case 0xd25f12: - case 0x163933: - case 0x2c4205: - case 0x454f3e: - case 0x687942: - return true - default: - break - } - - return false -} - -const isWoodedBiome = (biomePngData: Buffer, x: number, y: number, width: number) => { - const dataIndex = ((y * width) + x) * 4; - switch (rgb( - (biomePngData[dataIndex]), - (biomePngData[dataIndex + 1]), - (biomePngData[dataIndex + 2]), - )) { - case 0x056621: - case 0x537b09: - case 0x596651: - case 0x2d8e49: - return true; - default: - break - } - return false; -} - -const index = (x: number, y: number, width: number) => { - return ((y * width) + x) * 4; -} - -const landThreshold = 0x80 -const SHALLOW_WATER_THRESHOLD = 0xE0 - -const checkNeighboringLands = (waterMaskPngData: Buffer, x: number, y: number, width: number, height: number) => { - const ny = y === 0 ? 0 : y - 1; - const wx = (x + width - 1) % width; - const ex = (x + 1) % width ; - const sy = y === height - 1 ? height - 1 : y + 1; - - return { - nw: waterMaskPngData[index(wx, ny, width)] < landThreshold, - n: waterMaskPngData[index(x, ny, width)] < landThreshold, - ne: waterMaskPngData[index(ex, ny, width)] < landThreshold, - w: waterMaskPngData[index(wx, y, width)] < landThreshold, - i: waterMaskPngData[index(x, y, width)], - e: waterMaskPngData[index(ex, y, width)] < landThreshold, - sw: waterMaskPngData[index(wx, sy, width)] < landThreshold, - s: waterMaskPngData[index(x, sy, width)] < landThreshold, - se: waterMaskPngData[index(ex, sy, width)] < landThreshold, - }; -} - -const determineBackgroundTileType = (landFlags: ReturnType) => { - const islandThreshold = 0xE0; - if (landFlags.i < landThreshold) { - return 1; // 1 - } - - if ( - landFlags.i < islandThreshold - && !( - landFlags.nw - || landFlags.n - || landFlags.ne - || landFlags.w - || landFlags.e - || landFlags.sw - || landFlags.s - || landFlags.se - ) - ) { - return 29; // 29 - } - - let cursor = 0; - if (landFlags.n) { - cursor += 12; // 12 - if (landFlags.e) { - cursor += 12; // 24 - if (landFlags.s) { - cursor += 14; // 38 - if (landFlags.w) { - cursor -= 10; // 28 - } - } else if (landFlags.w) { - cursor += 16; // 40 - } else if (landFlags.sw) { - cursor += 12; // 36 - } - } else if (landFlags.w) { - cursor += 15; // 27 - if (landFlags.s) { - cursor += 12; // 39 - } else if (landFlags.se) { - cursor += 10; // 37 - } - } else if (landFlags.s) { - cursor += 31; // 43 - } else if (landFlags.se) { - cursor += 8; // 20 - if (landFlags.sw) { - cursor += 12; // 32 - } - } else if (landFlags.sw) { - cursor += 11; // 23 - } - } else if (landFlags.s) { - cursor += 13; // 13 - if (landFlags.e) { - cursor += 12; // 25 - if (landFlags.w) { - cursor += 16; // 41 - } else if (landFlags.nw) { - cursor += 10; // 35 - } - } else if (landFlags.w) { - cursor += 13; // 26 - if (landFlags.ne) { - cursor += 8; // 34 - } - } else if (landFlags.ne) { - cursor += 8; // 21 - if (landFlags.nw) { - cursor += 20; // 33 - } - } else if (landFlags.nw) { - cursor += 9; // 22 - } - } else if (landFlags.w) { - cursor += 14; // 14 - if (landFlags.e) { - cursor += 28; // 42 - } else if (landFlags.se) { - cursor += 4; // 18 - if (landFlags.ne) { - cursor += 13; // 31 - } - } else if (landFlags.ne) { - cursor += 5; // 19 - } - } else if (landFlags.e) { - cursor += 15; // 15 - if (landFlags.nw) { - cursor += 1 // 16 - if (landFlags.sw) { - cursor += 14; // 30 - } - } else if (landFlags.sw) { - cursor += 2; // 17 - } - } else if (landFlags.ne) { - cursor += 2; // 2 - if (landFlags.nw) { - cursor += 4; // 6 - if (landFlags.sw) { - cursor += 42; // 48 - if (landFlags.se) { - cursor -= 4; // 44 - } - } else if (landFlags.se) { - cursor += 39; // 45 - } - } else if (landFlags.se) { - cursor += 7; // 9 - if (landFlags.sw) { - cursor += 37; // 46 - } - } else if (landFlags.sw) { - cursor += 8; // 10 - } - } else if (landFlags.nw) { - cursor += 3; // 3 - if (landFlags.sw) { - cursor += 5; // 8 - if (landFlags.se) { - cursor += 39; // 47 - } - } else if (landFlags.se) { - cursor += 8; // 11 - } - } else if (landFlags.sw) { - cursor += 4; // 4 - if (landFlags.se) { - cursor += 3; // 7 - } - } else if (landFlags.se) { - cursor += 5; // 5 - } - - return cursor; + return destPath; } const generateWorldData = async (p: Projection, size: number, isWidthDimension: boolean) => { + await FileSystemServiceImpl.ensureDirectory('public/generated/resized'); + const tempResizedFiles = await Promise.all( + Object + .values(MapType) + .map(async (t: MapType) => resizeBaseData(t, p, size, isWidthDimension)) + ); + const mapTypeBuffers = await Promise.all(Object.values(MapType).map(async (t: MapType) => { return { mapType: t, @@ -401,101 +112,73 @@ const generateWorldData = async (p: Projection, size: number, isWidthDimension: initialItems: [] as number[][], } + const tileService: TileService = new TileServiceImpl( + data[MapType.BIOME].data, + data[MapType.ELEVATION].data, + data[MapType.WATER_MASK].data, + data[MapType.BATHYMETRY].data + ) + let i = 0; for (let y = 0; y < dy; y += 1) { worldData.spriteWaterLayer[y] = []; worldData.spriteBackgroundLayer[y] = []; worldData.spriteForegroundLayer[y] = []; for (let x = 0; x < dx; x += 1) { - const isShallowWater = data[MapType.BATHYMETRY].data[i] >= SHALLOW_WATER_THRESHOLD; - worldData.spriteWaterLayer[y][x] = isShallowWater ? WaterType.LIGHT : WaterType.DEFAULT; + worldData.spriteWaterLayer[y][x] = tileService.determineWaterBackgroundTileType(x, y, dx); - const landType = checkBiomeLandType(data[MapType.BIOME].data, x, y, dx); - const tileType = determineBackgroundTileType(checkNeighboringLands(data[MapType.WATER_MASK].data, x, y, dx, dy)); - worldData.spriteBackgroundLayer[y][x] = ((landType * 60) + tileType); - worldData.spriteForegroundLayer[y][x] = 0; - - if (tileType === 1) { - const isMountain = data[MapType.ELEVATION].data[i] >= 0x40 || isMountainBiome( - data[MapType.BIOME].data, - x, - y, - dx - ); - if (isMountain) { - switch (landType) { - case LandType.DESERT: - case LandType.MESA: - worldData.spriteForegroundLayer[y][x] = 796; - break; - case LandType.ICE: - worldData.spriteForegroundLayer[y][x] = 799; - break; - case LandType.FOREST: - case LandType.PLAINS: - default: - worldData.spriteForegroundLayer[y][x] = 797; - break; - } - } - - const isHill = isHillBiome(data[MapType.BIOME].data, x, y, dx); - if (isHill) { - if (landType === LandType.ICE) { - worldData.spriteForegroundLayer[y][x] = 903 - } else { - worldData.spriteForegroundLayer[y][x] = 901 - } - } - - const isWooded = isWoodedBiome(data[MapType.BIOME].data, x, y, dx); - if (isWooded) { - if (landType === LandType.ICE) { - worldData.spriteForegroundLayer[y][x] = 906 - } else { - worldData.spriteForegroundLayer[y][x] = 905 - } - } - } + const tileType = tileService.determineBackgroundTileType(x, y, dx, dy); + worldData.spriteBackgroundLayer[y][x] = tileType; + worldData.spriteForegroundLayer[y][x] = tileService.determineForegroundTileType(tileType, x, y, dx); i += 4; } } + await Promise.all(tempResizedFiles.map(f => fs.unlink(f))); + await fs.rmdir('public/generated/resized'); return worldData; } +const ensureDirectories = async () => { + await FileSystemServiceImpl.ensureDirectory('public/generated'); + await FileSystemServiceImpl.ensureDirectory('public/generated/base'); +} + +const determineDimension = (width: string, height: string) => { + if (width && Number.isFinite(Number(width))) { + return { + size: Number(width), + isWidthDimension: true, + } + } + if (height && Number.isFinite(Number(height))) { + return { + size: Number(height), + isWidthDimension: false, + } + } + throw new Error('Unspecified width or height'); +} + const generateHandler: NextApiHandler = async (req, res) => { const { projection = Projection.EQUIRECTANGULAR, width, height, + bounds = '-180,90;180,-90', } = req.query - let isWidthDimension: boolean; - let size: number; - if (width && Number.isFinite(Number(width))) { - size = Number(width); - isWidthDimension = true; - } else if (height && Number.isFinite(Number(height))) { - size = Number(height); - isWidthDimension = false; - } else { - throw new Error('Unspecified width or height'); - } + const { size, isWidthDimension } = determineDimension( + Array.isArray(width) ? width[0] : width, + Array.isArray(height) ? height[0] : height, + ); - await FileSystemServiceImpl.ensureDirectory('public/generated'); - await FileSystemServiceImpl.ensureDirectory('public/generated/base'); - await FileSystemServiceImpl.ensureDirectory('public/generated/resized'); - await FileSystemServiceImpl.ensureDirectory('public/worlds'); + await ensureDirectories(); const baseDataImageUrls = await Promise.all( Object .values(MapType) .map(async (t: MapType) => generateProjectedBaseData(t, projection as Projection)) ); - await Promise.all( - Object - .values(MapType) - .map(async (t: MapType) => resizeBaseData(t, projection as Projection, size, isWidthDimension)) - ); + const worldData = await generateWorldData(projection as Projection, size, isWidthDimension); res.json({ diff --git a/src/utils/types.ts b/src/utils/types.ts index cc0f716..1bc53b1 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -35,3 +35,13 @@ export enum WaterType { LIGHT = 5, LAVA = 6 } + +export enum LandType { + MESA = 0, + DESERT = 1, + FOREST = 2, + PLAINS = 3, + ICE = 4, + BEACH = 5, + MUD = 6, +}