Просмотр исходного кода

Refactor code

Organize code into services.
master
TheoryOfNekomata 2 лет назад
Родитель
Сommit
4c6005e64d
7 измененных файлов: 497 добавлений и 377 удалений
  1. +134
    -0
      src/backend/services/Biome.service.ts
  2. +9
    -0
      src/backend/services/Color.service.ts
  3. +10
    -6
      src/backend/services/Projection.service.ts
  4. +281
    -0
      src/backend/services/Tile.service.ts
  5. +0
    -1
      src/components/GenerateMapForm/index.tsx
  6. +53
    -370
      src/pages/api/generate/index.ts
  7. +10
    -0
      src/utils/types.ts

+ 134
- 0
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;
}
}

+ 9
- 0
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
}
}

+ 10
- 6
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, ProjectionData> = {
[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;


+ 281
- 0
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);
}
}

+ 0
- 1
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 (


+ 53
- 370
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<typeof checkNeighboringLands>) => {
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({


+ 10
- 0
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,
}

Загрузка…
Отмена
Сохранить