From 5658189008570610572589eb306e66193e40e82e Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 22 Oct 2024 05:14:12 +0000 Subject: [PATCH] fix: types/lint --- src/api/WorldComputeProxy.ts | 40 +- src/api/world-compute.ts | 216 ++++++--- src/common/types.ts | 73 +-- src/common/utils.ts | 116 +++-- src/datacontainers/ChunkContainer.ts | 478 +++++++++--------- src/datacontainers/GroundCache.ts | 36 +- src/datacontainers/GroundPatch.ts | 50 +- src/datacontainers/RandomDistributionMap.ts | 113 +++-- src/feats/BoardContainer.ts | 46 +- src/index.ts | 19 +- src/misc/ItemsInventory.ts | 128 ++--- src/misc/WorldConfig.ts | 9 +- src/procgen/Biome.ts | 143 +++--- src/procgen/BlueNoisePattern.ts | 2 +- src/procgen/Heightmap.ts | 3 +- src/third-party/nbt_custom.ts | 506 ++++++++++---------- src/tools/ChunkFactory.ts | 10 +- src/tools/ProceduralGenerators.ts | 27 +- src/tools/SchematicLoader.ts | 230 ++++----- tsconfig.json | 2 +- 20 files changed, 1209 insertions(+), 1038 deletions(-) diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index 32fe2d2..6e7fc1f 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,6 +1,6 @@ import { Box2, Vector2 } from 'three' -import { Block, PatchKey } from '../common/types' +import { GroundBlock, PatchKey } from '../common/types' import { GroundPatch, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { @@ -17,8 +17,8 @@ export type ComputeApiParams = Partial<{ }> /** - * World API frontend proxying requests to internal modules: world-compute, world-cache, - * When optional worker is provided all compute request are proxied to worker + * World API frontend proxying requests to internal modules: world-compute, world-cache, + * When optional worker is provided all compute request are proxied to worker * instead of main thread */ export class WorldComputeProxy { @@ -78,15 +78,15 @@ export class WorldComputeProxy { const blocks = !this.worker ? WorldCompute.computeBlocksBatch(blockPosBatch, params) : ((await this.workerCall(ComputeApiCall.BlocksBatchCompute, [ - blockPosBatch, - params, - ])?.then((blocksStubs: Block[]) => - // parse worker's data to recreate original objects - blocksStubs.map(blockStub => { - blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos) - return blockStub - }), - )) as Block[]) + blockPosBatch, + params, + ])?.then((blocksStubs: GroundBlock[]) => + // parse worker's data to recreate original objects + blocksStubs.map(blockStub => { + blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos) + return blockStub + }), + )) as GroundBlock[]) return blocks } @@ -95,9 +95,9 @@ export class WorldComputeProxy { const overgroundItems = !this.worker ? WorldCompute.retrieveOvergroundItems(queriedRegion) : await this.workerCall( - ComputeApiCall.OvergroundItemsQuery, - [queriedRegion], // [emptyPatch.bbox] - ) + ComputeApiCall.OvergroundItemsQuery, + [queriedRegion], // [emptyPatch.bbox] + ) return overgroundItems } @@ -106,11 +106,11 @@ export class WorldComputeProxy { const patch = !this.worker ? WorldCompute.bakePatch(patchKey) : ((await this.workerCall( - ComputeApiCall.PatchCompute, - [patchKey], // [emptyPatch.bbox] - )?.then(patchStub => - new GroundPatch().fromStub(patchStub), - )) as GroundPatch) + ComputeApiCall.PatchCompute, + [patchKey], // [emptyPatch.bbox] + )?.then(patchStub => + new GroundPatch().fromStub(patchStub), + )) as GroundPatch) yield patch } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 29b26a3..fd489cf 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,18 +1,45 @@ import { Box2, Vector2, Vector3 } from 'three' -import { GroundPatch, ItemsInventory, PseudoDistributionMap, WorldConf } from '../index' + +import { + GroundPatch, + ItemsInventory, + PseudoDistributionMap, + WorldConf, +} from '../index' import { Biome, BiomeInfluence, BiomeType, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { BiomeLandscapeElement, Block, PatchBoundId, PatchKey } from '../common/types' -import { asVect2, asVect3, bilinearInterpolation, getPatchBoundingPoints, getPatchId, serializePatchId } from '../common/utils' +import { + BlockData, + GroundBlock, + LandscapesConf, + PatchBoundId, + PatchKey, +} from '../common/types' +import { + asVect2, + asVect3, + bilinearInterpolation, + getPatchBoundingPoints, + getPatchId, + serializePatchId, +} from '../common/utils' import { ItemType } from '../misc/ItemsInventory' -import { DistributionProfile, DistributionProfiles } from '../datacontainers/RandomDistributionMap' +import { + DistributionProfile, + DistributionProfiles, +} from '../datacontainers/RandomDistributionMap' import { DistributionParams } from '../procgen/BlueNoisePattern' -import { BlockData } from '../datacontainers/GroundPatch' type PatchBoundingBiomes = Record -const defaultDistribution: DistributionParams = { ...DistributionProfiles[DistributionProfile.MEDIUM], minDistance: 10 } -const defaultSpawnMap = new PseudoDistributionMap(undefined, defaultDistribution) +const defaultDistribution: DistributionParams = { + ...DistributionProfiles[DistributionProfile.MEDIUM], + minDistance: 10, +} +const defaultSpawnMap = new PseudoDistributionMap( + undefined, + defaultDistribution, +) const defaultItemDims = new Vector3(10, 13, 10) /** @@ -31,41 +58,57 @@ const getBiomeBoundsInfluences = (bounds: Box2) => { const { xMyM, xMyP, xPyM, xPyP } = PatchBoundId // eval biome at patch corners const equals = (v1: BiomeInfluence, v2: BiomeInfluence) => { - const different = Object.keys(v1).find(k => v1[k as BiomeType] !== v2[k as BiomeType]) + const different = Object.keys(v1).find( + k => v1[k as BiomeType] !== v2[k as BiomeType], + ) return !different } const boundsPoints = getPatchBoundingPoints(bounds) const boundsInfluences = {} as PatchBoundingBiomes - [xMyM, xMyP, xPyM, xPyP].map(key => { + ;[xMyM, xMyP, xPyM, xPyP].map(key => { const boundPos = boundsPoints[key] as Vector2 const biomeInfluence = Biome.instance.getBiomeInfluence(boundPos) boundsInfluences[key] = biomeInfluence // const block = computeGroundBlock(asVect3(pos), biomeInfluence) return biomeInfluence }) - const allEquals = equals(boundsInfluences[xMyM], boundsInfluences[xPyM]) - && equals(boundsInfluences[xMyM], boundsInfluences[xMyP]) - && equals(boundsInfluences[xMyM], boundsInfluences[xPyP]) + const allEquals = + equals(boundsInfluences[xMyM], boundsInfluences[xPyM]) && + equals(boundsInfluences[xMyM], boundsInfluences[xMyP]) && + equals(boundsInfluences[xMyM], boundsInfluences[xPyP]) return allEquals ? boundsInfluences[xMyM] : boundsInfluences } -const getBlockBiome = (blockPos: Vector2, patchBounds: Box2, boundingBiomes: BiomeInfluence | PatchBoundingBiomes) => { - if ((boundingBiomes as PatchBoundingBiomes)[PatchBoundId.xMyM] && WorldConf.settings.useBiomeBilinearInterpolation) { - return bilinearInterpolation(blockPos, patchBounds, boundingBiomes as PatchBoundingBiomes) +const getBlockBiome = ( + blockPos: Vector2, + patchBounds: Box2, + boundingBiomes: BiomeInfluence | PatchBoundingBiomes, +) => { + if ( + (boundingBiomes as PatchBoundingBiomes)[PatchBoundId.xMyM] && + WorldConf.settings.useBiomeBilinearInterpolation + ) { + return bilinearInterpolation( + blockPos, + patchBounds, + boundingBiomes as PatchBoundingBiomes, + ) as BiomeInfluence } - return boundingBiomes + return boundingBiomes as BiomeInfluence } -export const computeGroundBlock = (blockPos: Vector3, biomeInfluence?: BiomeInfluence) => { +export const computeGroundBlock = ( + blockPos: Vector3, + biomeInfluence?: BiomeInfluence, +) => { biomeInfluence = biomeInfluence || Biome.instance.getBiomeInfluence(blockPos) // const biomeInfluenceBis = Biome.instance.getBiomeInfluence(blockPos) const biomeType = Biome.instance.getBiomeType(biomeInfluence) const rawVal = Heightmap.instance.getRawVal(blockPos) - const noiseLevel = Biome.instance.getBiomeConf(rawVal, biomeType) as BiomeLandscapeElement - const currLevelConf = noiseLevel.data - const prevLevelConf = noiseLevel.prev?.data - const nextLevelConf = noiseLevel.next?.data - const confKey = currLevelConf.key + const nominalConf = Biome.instance.getBiomeConf( + rawVal, + biomeType, + ) as LandscapesConf // const confIndex = Biome.instance.getConfIndex(currLevelConf.key) // const confData = Biome.instance.indexedConf.get(confIndex) const level = Heightmap.instance.getGroundLevel( @@ -73,33 +116,37 @@ export const computeGroundBlock = (blockPos: Vector3, biomeInfluence?: BiomeInfl rawVal, biomeInfluence, ) + let usedConf = nominalConf // const pos = new Vector3(blockPos.x, level, blockPos.z) - let type = currLevelConf.type - if (nextLevelConf) { - const variation = Biome.instance.posRandomizer.eval(blockPos.clone().multiplyScalar(50))//Math.cos(0.1 * blockPos.length()) / 100 - const min = new Vector2(currLevelConf.x, currLevelConf.y) - const max = new Vector2(nextLevelConf.x, nextLevelConf.y) + if (nominalConf.next?.data) { + const variation = Biome.instance.posRandomizer.eval( + blockPos.clone().multiplyScalar(50), + ) // Math.cos(0.1 * blockPos.length()) / 100 + const min = new Vector2(nominalConf.data.x, nominalConf.data.y) + const max = new Vector2(nominalConf.next.data.x, nominalConf.next.data.y) const rangeBox = new Box2(min, max) const dims = rangeBox.getSize(new Vector2()) // const slope = dims.y / dims.x const distRatio = (rawVal - min.x) / dims.x const threshold = 4 * distRatio - const prevType = prevLevelConf?.type - type = variation > threshold && prevType ? prevType : type + usedConf = + variation > threshold && nominalConf.prev?.data.type + ? nominalConf.prev + : nominalConf } - if (!type) { - console.log(currLevelConf) + if (isNaN(usedConf.data.type)) { + console.log(nominalConf.data) } // } // level += offset - const output = { level, type, confKey } + const output = { level, type: usedConf.data.type, confKey: usedConf.data.key } return output } /** - * Individual blocks + * Ground blocks */ /** @@ -113,12 +160,18 @@ export const computeBlocksBatch = async ( params = { includeEntitiesBlocks: false }, ) => { // sort blocks by patch - const blocksByPatch: Record = {} + const blocksByPatch: Record = {} const blocksBatch = blockPosBatch.map(pos => { - const patchKey = serializePatchId(getPatchId(pos, WorldConf.regularPatchDimensions)) - const block = { + const patchKey = serializePatchId( + getPatchId(pos, WorldConf.regularPatchDimensions), + ) + const data: BlockData = { + level: 0, + type: BlockType.NONE, + } + const block: GroundBlock = { pos: asVect3(pos), - data: null, + data, } blocksByPatch[patchKey] = blocksByPatch[patchKey] || [] blocksByPatch[patchKey]?.push(block as any) @@ -128,12 +181,19 @@ export const computeBlocksBatch = async ( const groundPatch = new GroundPatch(patchKey) const biomeBoundsInfluences = getBiomeBoundsInfluences(groundPatch.bounds) for await (const block of patchBlocks) { - const blockBiome = getBlockBiome(asVect2(block.pos), groundPatch.bounds, biomeBoundsInfluences) + const blockBiome = getBlockBiome( + asVect2(block.pos), + groundPatch.bounds, + biomeBoundsInfluences, + ) block.data = computeGroundBlock(block.pos, blockBiome) // override with last block if specified if (params.includeEntitiesBlocks) { const lastBlockData = await queryLastBlockData(asVect2(block.pos)) - block.data = lastBlockData.level > 0 && lastBlockData.type ? lastBlockData : block.data + block.data = + lastBlockData.level > 0 && lastBlockData.type + ? lastBlockData + : block.data } block.pos.y = block.data.level } @@ -161,11 +221,11 @@ export const computeBlocksBatch = async ( // } // return block // }) - return blocksBatch + return blocksBatch as GroundBlock[] } /** - * Patch requests + * Ground patch */ export const bakePatch = (boundsOrPatchKey: PatchKey | Box2) => { @@ -180,34 +240,42 @@ export const bakePatch = (boundsOrPatchKey: PatchKey | Box2) => { export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { const groundPatch = new GroundPatch(boundsOrPatchKey) const biomeBoundsInfluences = getBiomeBoundsInfluences(groundPatch.bounds) - const { min, max } = groundPatch.bounds const blocks = groundPatch.iterBlocksQuery(undefined, false) - const level = { + const valueRange = { min: 512, max: 0, } let blockIndex = 0 for (const block of blocks) { - // EXPERIMENTAL: is it faster to perform bilinear interpolation rather than sampling biome for each block? - + // EXPERIMENTAL: is it faster to perform bilinear interpolation rather + // than sampling biome for each block? // if biome is the same at each patch corners, no need to interpolate - const blockBiome = getBlockBiome(asVect2(block.pos), groundPatch.bounds, biomeBoundsInfluences) + const blockBiome = getBlockBiome( + asVect2(block.pos), + groundPatch.bounds, + biomeBoundsInfluences, + ) const blockData = computeGroundBlock(block.pos, blockBiome) - level.min = Math.min(min.y, blockData.level) - level.max = Math.max(max.y, blockData.level) + valueRange.min = Math.min(valueRange.min, blockData.level) + valueRange.max = Math.max(valueRange.max, blockData.level) groundPatch.writeBlockData(blockIndex, blockData) blockIndex++ } return groundPatch } -// Patch overground layer +/** + * Overground patch (items) + */ export const retrieveOvergroundItems = async (bounds: Box2) => { const boundsBiomeInfluences = getBiomeBoundsInfluences(bounds) const spawnedItems: Record = {} - const spawnPlaces = defaultSpawnMap.querySpawnLocations(bounds, asVect2(defaultItemDims)) - spawnPlaces.map(pos => { + const spawnPlaces = defaultSpawnMap.querySpawnLocations( + bounds, + asVect2(defaultItemDims), + ) + for (const pos of spawnPlaces) { const blockBiome = getBlockBiome(pos, bounds, boundsBiomeInfluences) const { confKey, level } = computeGroundBlock(asVect3(pos), blockBiome) const weightedItems = Biome.instance.indexedConf.get(confKey)?.data?.flora @@ -219,39 +287,62 @@ export const retrieveOvergroundItems = async (bounds: Box2) => { spawnWeight-- } }) - const itemType = defaultSpawnMap.getSpawnedItem(pos, spawnableTypes) as ItemType + const itemType = defaultSpawnMap.getSpawnedItem( + pos, + spawnableTypes, + ) as ItemType if (itemType) { spawnedItems[itemType] = spawnedItems[itemType] || [] spawnedItems[itemType]?.push(asVect3(pos, level)) } } - }) + } return spawnedItems } export const queryLastBlockData = async (queriedLoc: Vector2) => { const lastBlockData: BlockData = { level: 0, - type: 0 + type: 0, } - const spawnPlaces = defaultSpawnMap.querySpawnLocations(queriedLoc, asVect2(defaultItemDims)) + const spawnPlaces = defaultSpawnMap.querySpawnLocations( + queriedLoc, + asVect2(defaultItemDims), + ) for await (const spawnOrigin of spawnPlaces) { - const patchKey = serializePatchId(getPatchId(spawnOrigin, WorldConf.regularPatchDimensions)) + const patchKey = serializePatchId( + getPatchId(spawnOrigin, WorldConf.regularPatchDimensions), + ) const groundPatch = new GroundPatch(patchKey) const biomeBoundsInfluences = getBiomeBoundsInfluences(groundPatch.bounds) - const blockBiome = getBlockBiome(spawnOrigin, groundPatch.bounds, biomeBoundsInfluences) - const { confKey, level } = computeGroundBlock(asVect3(spawnOrigin), blockBiome) - let spawnableTypes = Biome.instance.indexedConf.get(confKey)?.data?.flora + const blockBiome = getBlockBiome( + spawnOrigin, + groundPatch.bounds, + biomeBoundsInfluences, + ) + const { confKey, level } = computeGroundBlock( + asVect3(spawnOrigin), + blockBiome, + ) + const spawnableTypes = Biome.instance.indexedConf.get(confKey)?.data?.flora const spawnableItems: ItemType[] = [] - for (let [itemType, spawnWeight] of Object.entries(spawnableTypes || {})) { + for (const entry of Object.entries(spawnableTypes || {})) { + const [itemType] = entry + let [, spawnWeight] = entry while (spawnWeight > 0) { spawnableItems.push(itemType) spawnWeight-- } } - const itemType = defaultSpawnMap.getSpawnedItem(spawnOrigin, spawnableItems) as ItemType + const itemType = defaultSpawnMap.getSpawnedItem( + spawnOrigin, + spawnableItems, + ) as ItemType if (itemType && spawnOrigin) { - const itemChunk = await ItemsInventory.getInstancedChunk(itemType, asVect3(spawnOrigin)) + const itemChunk = await ItemsInventory.getInstancedChunk( + itemType, + asVect3(spawnOrigin), + ) if (itemChunk) { // const halfDims = itemTemplateChunk.bounds.getSize(new Vector3()).divideScalar(2) // const chunkOrigin = spawnOrigin.clone().sub(asVect2(halfDims)).round() @@ -273,7 +364,6 @@ export const queryLastBlockData = async (queriedLoc: Vector2) => { return lastBlockData } - // Battle board // export const computeBoardData = (boardPos: Vector3, boardParams: BoardInputParams, lastBoardBounds: Box2) => { // const boardMap = new BoardContainer(boardPos, boardParams, lastBoardBounds) diff --git a/src/common/types.ts b/src/common/types.ts index de24359..cc9b34d 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,18 +1,29 @@ import { Vector2, Vector3 } from 'three' -import { BlockData } from '../datacontainers/GroundPatch' import { ItemType } from '../misc/ItemsInventory' import { BiomeType, BlockType } from '../procgen/Biome' import { LinkedList } from './misc' -export type Block = { +export type Block = { pos: Vector3 - data: BlockData - buffer?: Uint16Array + data: T } -export type PatchBlock = Block & { +export enum BlockMode { + DEFAULT, + BOARD_CONTAINER, +} + +export type BlockData = { + level: number + type: BlockType + mode?: BlockMode +} + +export type GroundBlock = Block + +export type PatchBlock = GroundBlock & { index: number localPos: Vector3 } @@ -31,9 +42,7 @@ export enum IntercardinalDirections { SW, } -export type AllCardinalDirections = - | CardinalDirections - | IntercardinalDirections +export type AllCardinalDirections = CardinalDirections | IntercardinalDirections // export enum SurfaceBounds { // R_DOWN, // xM,yM @@ -43,15 +52,14 @@ export type AllCardinalDirections = // } export enum PatchBoundId { - xMyM = "xMyM", - xMyP = "xMyP", - xPyP = "xPyP", - xPyM = "xPyM", + xMyM = 'xMyM', + xMyP = 'xMyP', + xPyP = 'xPyP', + xPyM = 'xPyM', } export type PatchBoundingPoints = Record - export enum ChunkBoundId { xMyMzM, xMyPzM, @@ -125,6 +133,11 @@ export type ProcLayerExtCfg = { harmonic_spread: number } +export type PatchKey = string +export type PatchId = Vector2 +export type ChunkKey = string +export type ChunkId = Vector3 + // export enum TerrainType { // SEA, // BEACH, @@ -136,27 +149,23 @@ export type ProcLayerExtCfg = { // MOUNTAINS_TOP, // } +export type LandscapeId = string // landscape id assigned to noise level +export type BiomeLandscapeKey = string // combination of biomeType and LandscapeId + export type LandscapeFields = { - key: BiomeLandscapeKey, - x: number, // noise value - y: number, // height noise mapping - type: BlockType, // ground surface - subtype: BlockType, // below ground or mixed with ground surface - mixratio: number, // mixing ratio between type/subtype - flora?: Record, - fadein: any, + key: BiomeLandscapeKey + x: number // noise value + y: number // height noise mapping + type: BlockType // ground surface + subtype: BlockType // below ground or mixed with ground surface + mixratio: number // mixing ratio between type/subtype + flora?: Record + fadein: any fadeout: any } // Biome landscapes mappings -export type BiomeLandscapes = Record> -export type BiomeConfigs = Record -export type BiomeLandscapeElement = LinkedList - -export type LandscapeId = string // landscape id assigned to noise level -export type BiomeLandscapeKey = string // combination of biomeType and LandscapeId - -export type PatchKey = string -export type PatchId = Vector2 -export type ChunkKey = string -export type ChunkId = Vector3 +export type LandscapesRawConf = Record> +export type BiomesRawConf = Record +export type LandscapesConf = LinkedList +export type BiomesConf = Record diff --git a/src/common/utils.ts b/src/common/utils.ts index 5745e5e..7b19274 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -5,12 +5,12 @@ import { VolumeNeighbour, ChunkId, ChunkKey, - BiomeTerrainConfigFields, - NoiseLevelConf, PatchId, PatchKey, PatchBoundId, PatchBoundingPoints, + LandscapeFields, + LandscapesConf, } from './types' // Clamp number between two values: @@ -34,20 +34,22 @@ const vectRoundToDec = (input: Vector2 | Vector3, n_pow: number) => { } const smoothstep = (edge0: number, edge1: number, x: number) => { - const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0))); - return t * t * (3 - 2 * t); -}; + const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0))) + return t * t * (3 - 2 * t) +} // const MappingRangeFinder = (item: LinkedList, inputVal: number) => item.next && inputVal > (item.next.data as MappingData).x -export const MappingRangeSorter = (item1: BiomeTerrainConfigFields, item2: BiomeTerrainConfigFields) => - item1.x - item2.x +export const MappingRangeSorter = ( + item1: LandscapeFields, + item2: LandscapeFields, +) => item1.x - item2.x /** * find element with inputVal withing interpolation range * @param inputVal * @returns */ -const findMatchingRange = (inputVal: number, noiseMappings: NoiseLevelConf) => { +const findMatchingRange = (inputVal: number, noiseMappings: LandscapesConf) => { let match = noiseMappings.first() while (match.next && inputVal > match.next.data.x) { match = match.next @@ -56,75 +58,83 @@ const findMatchingRange = (inputVal: number, noiseMappings: NoiseLevelConf) => { } /** - * + * * y2 - p12-----p22 * | + p | * | | * y1 - p11-----p21 * | | * x1 x2 - * - * @param p - * @param p11 - * @param p12 - * @param p22 - * @param p21 - * @returns + * + * @param p + * @param p11 + * @param p12 + * @param p22 + * @param p21 + * @returns */ -const bilinearInterpolation = (p: Vector2, bounds: Box2, boundingVals: Record) => { +const bilinearInterpolation = ( + p: Vector2, + bounds: Box2, + boundingVals: Record>, +) => { const { x, y } = p const { x: x1, y: y1 } = bounds.min const { x: x2, y: y2 } = bounds.max const dims = bounds.getSize(new Vector2()) - const sumComponents = (componentKey, values) => { - return values.reduce((sum, val) => sum + val[componentKey], 0) + const sumComponents = ( + componentKey: string, + values: Record[], + ) => { + return values.reduce((sum, val) => sum + (val[componentKey] || 0), 0) } - const add = (...items) => { + const add = (...items: Record[]) => { const res: any = {} const [first] = items - Object.keys(first).forEach(k => res[k] = sumComponents(k, items)) + first && Object.keys(first).forEach(k => (res[k] = sumComponents(k, items))) return res } - const mul = (w: number, v: T) => { + + const mul = (v: Record, w: number) => { const res = { ...v } - Object.keys(res).forEach(k => res[k] *= w) + Object.keys(res).forEach(k => (res[k] = (res[k] as number) * w)) return res } const divider = dims.x * dims.y // common divider - const w11 = (x2 - x) * (y2 - y) / divider - const w12 = (x2 - x) * (y - y1) / divider - const w21 = (x - x1) * (y2 - y) / divider - const w22 = (x - x1) * (y - y1) / divider - const m11 = mul(w11, boundingVals.xMyM) - const m12 = mul(w12, boundingVals.xMyP) - const m21 = mul(w21, boundingVals.xPyM) - const m22 = mul(w22, boundingVals.xPyP) + const w11 = ((x2 - x) * (y2 - y)) / divider + const w12 = ((x2 - x) * (y - y1)) / divider + const w21 = ((x - x1) * (y2 - y)) / divider + const w22 = ((x - x1) * (y - y1)) / divider + const m11 = mul(boundingVals.xMyM, w11) + const m12 = mul(boundingVals.xMyP, w12) + const m21 = mul(boundingVals.xPyM, w21) + const m22 = mul(boundingVals.xPyP, w22) const res = add(m11, m12, m21, m22) return res } /** * Inverse distance weighting (IDW) - * @param cornersPoints - * @param point + * @param cornersPoints + * @param point */ -const invDistWeighting = (cornerPointsValues: [p: Vector2, v: any][], point: Vector2) => { - const [firstItem] = cornerPointsValues - const [, firstVal] = firstItem || [] - const initVal = { ...firstVal } - Object.keys(initVal).forEach(key => initVal[key] = 0) - let totalWeight = 0 - const idwInterpolation = cornerPointsValues.reduce((weightedSum, [p, v]) => { - const d = point.distanceTo(p) - const w = d > 0 ? 1 / d : 1 - Object.keys(weightedSum).forEach(k => weightedSum[k] += w * v[k]) - totalWeight += w - return weightedSum - }, initVal) - Object.keys(idwInterpolation).forEach(key => idwInterpolation[key] = idwInterpolation[key] / totalWeight) - return idwInterpolation -} +// const invDistWeighting = (cornerPointsValues: [p: Vector2, v: any][], point: Vector2) => { +// const [firstItem] = cornerPointsValues +// const [, firstVal] = firstItem || [] +// const initVal = { ...firstVal } +// Object.keys(initVal).forEach(key => initVal[key] = 0) +// let totalWeight = 0 +// const idwInterpolation = cornerPointsValues.reduce((weightedSum, [p, v]) => { +// const d = point.distanceTo(p) +// const w = d > 0 ? 1 / d : 1 +// Object.keys(weightedSum).forEach(k => weightedSum[k] += w * v[k]) +// totalWeight += w +// return weightedSum +// }, initVal) +// Object.keys(idwInterpolation).forEach(key => idwInterpolation[key] = idwInterpolation[key] / totalWeight) +// return idwInterpolation +// } /** * Orthogonal or direct 2D neighbours e.g. @@ -351,10 +361,10 @@ const parseBox3Stub = (stub: Box3) => { const parseThreeStub = (stub: any) => { return stub ? parseBox3Stub(stub) || - parseVect3Stub(stub) || - parseBox2Stub(stub) || - parseVect2Stub(stub) || - stub + parseVect3Stub(stub) || + parseBox2Stub(stub) || + parseVect2Stub(stub) || + stub : stub } diff --git a/src/datacontainers/ChunkContainer.ts b/src/datacontainers/ChunkContainer.ts index 0ef6cdd..df1bdb1 100644 --- a/src/datacontainers/ChunkContainer.ts +++ b/src/datacontainers/ChunkContainer.ts @@ -1,280 +1,292 @@ -import { Vector2, Box2, Box3, Vector3, MathUtils } from 'three' +import { Vector2, Box3, Vector3 } from 'three' + import { ChunkId, ChunkKey } from '../common/types' -import { asVect3, chunkBoxFromKey, parseChunkKey, serializeChunkId } from '../common/utils' +import { + asVect3, + chunkBoxFromKey, + parseChunkKey, + serializeChunkId, +} from '../common/utils' import { WorldConf } from '../misc/WorldConfig' enum ChunkAxisOrder { - ZXY, - ZYX + ZXY, + ZYX, } /** * Low level multi-purpose data container */ export class ChunkContainer { - bounds: Box3 - dimensions: Vector3 - margin = 0 - chunkKey = '' // needed for chunk export - chunkId: ChunkId | undefined - rawData: Uint16Array - axisOrder: ChunkAxisOrder - - constructor(boundsOrChunkKey: Box3 | ChunkKey = new Box3(), margin = 0, axisOrder = ChunkAxisOrder.ZXY) { - //, bitLength = BitLength.Uint16) { - const bounds = - boundsOrChunkKey instanceof Box3 - ? boundsOrChunkKey.clone() - : chunkBoxFromKey(boundsOrChunkKey, WorldConf.defaultChunkDimensions) - this.bounds = bounds - this.dimensions = bounds.getSize(new Vector3()) - this.rawData = new Uint16Array(this.extendedDims.x * this.extendedDims.y * this.extendedDims.z) - this.margin = margin - this.axisOrder = axisOrder - const chunkId = - typeof boundsOrChunkKey === 'string' - ? parseChunkKey(boundsOrChunkKey) - : null - if (chunkId) { - this.id = chunkId - } - // this.rawData = getArrayConstructor(bitLength) - } + bounds: Box3 + dimensions: Vector3 + margin = 0 + chunkKey = '' // needed for chunk export + chunkId: ChunkId | undefined + rawData: Uint16Array + axisOrder: ChunkAxisOrder - get id() { - return this.chunkId + constructor( + boundsOrChunkKey: Box3 | ChunkKey = new Box3(), + margin = 0, + axisOrder = ChunkAxisOrder.ZXY, + ) { + //, bitLength = BitLength.Uint16) { + const bounds = + boundsOrChunkKey instanceof Box3 + ? boundsOrChunkKey.clone() + : chunkBoxFromKey(boundsOrChunkKey, WorldConf.defaultChunkDimensions) + this.bounds = bounds + this.dimensions = bounds.getSize(new Vector3()) + this.rawData = new Uint16Array( + this.extendedDims.x * this.extendedDims.y * this.extendedDims.z, + ) + this.margin = margin + this.axisOrder = axisOrder + const chunkId = + typeof boundsOrChunkKey === 'string' + ? parseChunkKey(boundsOrChunkKey) + : null + if (chunkId) { + this.id = chunkId } + // this.rawData = getArrayConstructor(bitLength) + } - set id(chunkId: Vector3 | undefined) { - this.chunkId = chunkId - this.chunkKey = serializeChunkId(chunkId) - } + get id() { + return this.chunkId + } - get extendedBounds() { - return this.bounds.clone().expandByScalar(this.margin) - } + set id(chunkId: Vector3 | undefined) { + this.chunkId = chunkId + this.chunkKey = chunkId ? serializeChunkId(chunkId) : '' + } - get extendedDims() { - return this.extendedBounds.getSize(new Vector3()) - } + get extendedBounds() { + return this.bounds.clone().expandByScalar(this.margin) + } - get localBox() { - const localBox = new Box3(new Vector3(0), this.dimensions.clone()) - return localBox - } + get extendedDims() { + return this.extendedBounds.getSize(new Vector3()) + } - get localExtendedBox() { - return this.localBox.expandByScalar(this.margin) - } + get localBox() { + const localBox = new Box3(new Vector3(0), this.dimensions.clone()) + return localBox + } - init(bounds: Box3) { - this.bounds = bounds - this.dimensions = bounds.getSize(new Vector3()) + get localExtendedBox() { + return this.localBox.expandByScalar(this.margin) + } + + init(bounds: Box3) { + this.bounds = bounds + this.dimensions = bounds.getSize(new Vector3()) + } + + // copy occurs only on the overlapping region of both containers + static copySourceToTarget( + sourceChunk: ChunkContainer, + targetChunk: ChunkContainer, + ) { + const adjustOverlapMargins = (overlap: Box3) => { + const margin = Math.min(targetChunk.margin, sourceChunk.margin) || 0 + overlap.min.x -= targetChunk.bounds.min.x === overlap.min.x ? margin : 0 + overlap.min.y -= targetChunk.bounds.min.y === overlap.min.y ? margin : 0 + overlap.min.z -= targetChunk.bounds.min.z === overlap.min.z ? margin : 0 + overlap.max.x += targetChunk.bounds.max.x === overlap.max.x ? margin : 0 + overlap.max.y += targetChunk.bounds.max.y === overlap.max.y ? margin : 0 + overlap.max.z += targetChunk.bounds.max.z === overlap.max.z ? margin : 0 } - // copy occurs only on the overlapping region of both containers - static copySourceToTarget(sourceChunk: ChunkContainer, targetChunk: ChunkContainer){ - const adjustOverlapMargins = (overlap: Box3) => { - const margin = Math.min(targetChunk.margin, sourceChunk.margin) || 0 - overlap.min.x -= targetChunk.bounds.min.x === overlap.min.x ? margin : 0 - overlap.min.y -= targetChunk.bounds.min.y === overlap.min.y ? margin : 0 - overlap.min.z -= targetChunk.bounds.min.z === overlap.min.z ? margin : 0 - overlap.max.x += targetChunk.bounds.max.x === overlap.max.x ? margin : 0 - overlap.max.y += targetChunk.bounds.max.y === overlap.max.y ? margin : 0 - overlap.max.z += targetChunk.bounds.max.z === overlap.max.z ? margin : 0 - } - - if (sourceChunk.bounds.intersectsBox(targetChunk.bounds)) { - const overlap = targetChunk.bounds.clone().intersect(sourceChunk.bounds) - adjustOverlapMargins(overlap) - - for (let { z } = overlap.min; z < overlap.max.z; z++) { - for (let { x } = overlap.min; x < overlap.max.x; x++) { - const globalStartPos = new Vector3(x, overlap.min.y, z) - const targetLocalStartPos = targetChunk.toLocalPos(globalStartPos) - const sourceLocalStartPos = sourceChunk.toLocalPos(globalStartPos) - let targetIndex = targetChunk.getIndex(targetLocalStartPos) - let sourceIndex = sourceChunk.getIndex(sourceLocalStartPos) - - for (let { y } = overlap.min; y < overlap.max.y; y++) { - const sourceVal = sourceChunk.rawData[sourceIndex] - if (sourceVal) { - targetChunk.rawData[targetIndex] = sourceVal - } - sourceIndex++ - targetIndex++ - } - } + if (sourceChunk.bounds.intersectsBox(targetChunk.bounds)) { + const overlap = targetChunk.bounds.clone().intersect(sourceChunk.bounds) + adjustOverlapMargins(overlap) + + for (let { z } = overlap.min; z < overlap.max.z; z++) { + for (let { x } = overlap.min; x < overlap.max.x; x++) { + const globalStartPos = new Vector3(x, overlap.min.y, z) + const targetLocalStartPos = targetChunk.toLocalPos(globalStartPos) + const sourceLocalStartPos = sourceChunk.toLocalPos(globalStartPos) + let targetIndex = targetChunk.getIndex(targetLocalStartPos) + let sourceIndex = sourceChunk.getIndex(sourceLocalStartPos) + + for (let { y } = overlap.min; y < overlap.max.y; y++) { + const sourceVal = sourceChunk.rawData[sourceIndex] + if (sourceVal) { + targetChunk.rawData[targetIndex] = sourceVal } + sourceIndex++ + targetIndex++ + } } + } } - /** - * - * @param localPos queried buffer location as Vector2 or Vector3 - * @returns buffer or block index for Vector2 and Vector3 input types, respectively. - */ - getIndex(localPos: Vector2 | Vector3) { - localPos = localPos instanceof Vector3 ? localPos : asVect3(localPos) - return localPos.z * this.dimensions.x * this.dimensions.y + localPos.x * this.dimensions.y + localPos.y - } + } - getLocalPosFromIndex(index: number) { - // const xy = this.dimensions.x*this.dimensions.y - // const z = Math.floor(index / xy) - // const x = Math.floor((index-z) / this.dimensions.y) - // const y = index % this.dimensions.x - // return new Vector3(x, y, z) - } + /** + * + * @param localPos queried buffer location as Vector2 or Vector3 + * @returns buffer or block index for Vector2 and Vector3 input types, respectively. + */ + getIndex(localPos: Vector2 | Vector3) { + localPos = localPos instanceof Vector3 ? localPos : asVect3(localPos) + return ( + localPos.z * this.dimensions.x * this.dimensions.y + + localPos.x * this.dimensions.y + + localPos.y + ) + } - toLocalPos(pos: Vector3) { - const origin = this.bounds.min.clone() - return pos.clone().sub(origin) - } + toLocalPos(pos: Vector3) { + const origin = this.bounds.min.clone() + return pos.clone().sub(origin) + } - toWorldPos(pos: Vector3) { - const origin = this.bounds.min.clone() - return origin.add(pos) - } + toWorldPos(pos: Vector3) { + const origin = this.bounds.min.clone() + return origin.add(pos) + } - inLocalRange(localPos: Vector3) { - return ( - localPos.x >= 0 && - localPos.x < this.dimensions.x && - localPos.y >= 0 && - localPos.y < this.dimensions.y && - localPos.z >= 0 && - localPos.z < this.dimensions.z - ) - } + inLocalRange(localPos: Vector3) { + return ( + localPos.x >= 0 && + localPos.x < this.dimensions.x && + localPos.y >= 0 && + localPos.y < this.dimensions.y && + localPos.z >= 0 && + localPos.z < this.dimensions.z + ) + } - inWorldRange(globalPos: Vector3) { - return ( - globalPos.x >= this.bounds.min.x && - globalPos.x < this.bounds.max.x && - globalPos.y >= this.bounds.min.y && - globalPos.y < this.bounds.max.y && - globalPos.z >= this.bounds.min.z && - globalPos.z < this.bounds.max.z - ) - } + inWorldRange(globalPos: Vector3) { + return ( + globalPos.x >= this.bounds.min.x && + globalPos.x < this.bounds.max.x && + globalPos.y >= this.bounds.min.y && + globalPos.y < this.bounds.max.y && + globalPos.z >= this.bounds.min.z && + globalPos.z < this.bounds.max.z + ) + } - isOverlapping(bounds: Box3) { - const nonOverlapping = - this.bounds.max.x <= bounds.min.x || - this.bounds.min.x >= bounds.max.x || - this.bounds.max.y <= bounds.min.y || - this.bounds.min.y >= bounds.max.y || - this.bounds.max.z <= bounds.min.z || - this.bounds.min.z >= bounds.max.z - return !nonOverlapping - } + isOverlapping(bounds: Box3) { + const nonOverlapping = + this.bounds.max.x <= bounds.min.x || + this.bounds.min.x >= bounds.max.x || + this.bounds.max.y <= bounds.min.y || + this.bounds.min.y >= bounds.max.y || + this.bounds.max.z <= bounds.min.z || + this.bounds.min.z >= bounds.max.z + return !nonOverlapping + } - containsPoint(pos: Vector3) { - // return this.bounds.containsPoint(pos) - return ( - pos.x >= this.bounds.min.x && - pos.y >= this.bounds.min.y && - pos.z >= this.bounds.min.z && - pos.x < this.bounds.max.x && - pos.y < this.bounds.max.y && - pos.z < this.bounds.max.z - ) - } + containsPoint(pos: Vector3) { + // return this.bounds.containsPoint(pos) + return ( + pos.x >= this.bounds.min.x && + pos.y >= this.bounds.min.y && + pos.z >= this.bounds.min.z && + pos.x < this.bounds.max.x && + pos.y < this.bounds.max.y && + pos.z < this.bounds.max.z + ) + } - adjustInputBounds(input: Box3 | Vector3, local = false) { - const rangeBox = input instanceof Box3 ? input : new Box3(input, input) - const { min, max } = local ? this.localBox : this.bounds - const rangeMin = new Vector3( - Math.max(Math.floor(rangeBox.min.x), min.x), - Math.max(Math.floor(rangeBox.min.y), min.y), - Math.max(Math.floor(rangeBox.min.z), min.z), - ) - const rangeMax = new Vector3( - Math.min(Math.floor(rangeBox.max.x), max.x), - Math.min(Math.floor(rangeBox.max.y), max.y), - Math.min(Math.floor(rangeBox.max.z), max.z), - ) - return local - ? new Box3(rangeMin, rangeMax) - : new Box3(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) - } + adjustInputBounds(input: Box3 | Vector3, local = false) { + const rangeBox = input instanceof Box3 ? input : new Box3(input, input) + const { min, max } = local ? this.localBox : this.bounds + const rangeMin = new Vector3( + Math.max(Math.floor(rangeBox.min.x), min.x), + Math.max(Math.floor(rangeBox.min.y), min.y), + Math.max(Math.floor(rangeBox.min.z), min.z), + ) + const rangeMax = new Vector3( + Math.min(Math.floor(rangeBox.max.x), max.x), + Math.min(Math.floor(rangeBox.max.y), max.y), + Math.min(Math.floor(rangeBox.max.z), max.z), + ) + return local + ? new Box3(rangeMin, rangeMax) + : new Box3(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) + } - /** + /** * iterate raw data * @param rangeBox iteration range as global coords * @param skipMargin */ - *iterateContent(iteratedBounds?: Box3 | Vector3, skipMargin = true) { - // convert to local coords to speed up iteration - const localBounds = iteratedBounds - ? this.adjustInputBounds(iteratedBounds) - : this.localExtendedBox - - const isMarginBlock = ({ x, y, z }: { x: number; y: number, z: number }) => - !iteratedBounds && - this.margin > 0 && - (x === localBounds.min.x || - x === localBounds.max.x - 1 || - y === localBounds.min.y || - y === localBounds.max.y - 1 || - z === localBounds.min.z || - z === localBounds.max.z - 1) - - let index = 0 - for (let { z } = localBounds.min; z < localBounds.max.z; z++) { - for (let { x } = localBounds.min; x < localBounds.max.x; x++) { - for (let { y } = localBounds.min; y < localBounds.max.y; y++) { - const localPos = new Vector3(x, y, z) - if (!skipMargin || !isMarginBlock(localPos)) { - index = iteratedBounds ? this.getIndex(localPos) : index - const rawData = this.rawData[index] - const res = { - pos: this.toWorldPos(localPos), - localPos, - index, - rawData, - } - yield res - } - index++ - } + *iterateContent(iteratedBounds?: Box3 | Vector3, skipMargin = true) { + // convert to local coords to speed up iteration + const localBounds = iteratedBounds + ? this.adjustInputBounds(iteratedBounds) + : this.localExtendedBox + + const isMarginBlock = ({ x, y, z }: { x: number; y: number; z: number }) => + !iteratedBounds && + this.margin > 0 && + (x === localBounds.min.x || + x === localBounds.max.x - 1 || + y === localBounds.min.y || + y === localBounds.max.y - 1 || + z === localBounds.min.z || + z === localBounds.max.z - 1) + + let index = 0 + for (let { z } = localBounds.min; z < localBounds.max.z; z++) { + for (let { x } = localBounds.min; x < localBounds.max.x; x++) { + for (let { y } = localBounds.min; y < localBounds.max.y; y++) { + const localPos = new Vector3(x, y, z) + if (!skipMargin || !isMarginBlock(localPos)) { + index = iteratedBounds ? this.getIndex(localPos) : index + const rawData = this.rawData[index] + const res = { + pos: this.toWorldPos(localPos), + localPos, + index, + rawData, } + yield res + } + index++ } + } } + } - encodeSectorData(sectorData: number) { - return sectorData - } + encodeSectorData(sectorData: number) { + return sectorData + } - decodeSectorData(sectorData: number) { - return sectorData - } + decodeSectorData(sectorData: number) { + return sectorData + } - readSector(pos: Vector3) { - const sectorIndex = this.getIndex(this.toLocalPos(pos)) - const rawData = this.rawData[sectorIndex] as number - return this.decodeSectorData(rawData) - } + readSector(pos: Vector3) { + const sectorIndex = this.getIndex(this.toLocalPos(pos)) + const rawData = this.rawData[sectorIndex] as number + return this.decodeSectorData(rawData) + } - writeSector(pos: Vector3, sectorData: number) { - const sectorIndex = this.getIndex(this.toLocalPos(pos)) - this.rawData[sectorIndex] = this.encodeSectorData(sectorData) - } + writeSector(pos: Vector3, sectorData: number) { + const sectorIndex = this.getIndex(this.toLocalPos(pos)) + this.rawData[sectorIndex] = this.encodeSectorData(sectorData) + } - readBuffer(localPos: Vector2) { - const buffIndex = this.getIndex(localPos) - const rawBuffer = this.rawData.slice(buffIndex, buffIndex + this.dimensions.y) - return rawBuffer - } + readBuffer(localPos: Vector2) { + const buffIndex = this.getIndex(localPos) + const rawBuffer = this.rawData.slice( + buffIndex, + buffIndex + this.dimensions.y, + ) + return rawBuffer + } - writeBuffer( - localPos: Vector2, - buffer: Uint16Array, - ) { - const buffIndex = this.getIndex(localPos) - this.rawData.set(buffer, buffIndex) - } + writeBuffer(localPos: Vector2, buffer: Uint16Array) { + const buffIndex = this.getIndex(localPos) + this.rawData.set(buffer, buffIndex) + } - // abstract get chunkIds(): ChunkId[] - // abstract toChunks(): any + // abstract get chunkIds(): ChunkId[] + // abstract toChunks(): any } diff --git a/src/datacontainers/GroundCache.ts b/src/datacontainers/GroundCache.ts index 5488256..dc10467 100644 --- a/src/datacontainers/GroundCache.ts +++ b/src/datacontainers/GroundCache.ts @@ -47,10 +47,11 @@ export class PatchesContainer> { * Returns block from cache if found, and precache near blocks if needed * If not found will compute patch containing block first, * and return a promise that will resolve once patch is available in cache - * @param blockPos - * @param params + * @param blockPos + * @param params */ export class GroundCache extends PatchesContainer { + // eslint-disable-next-line no-use-before-define static singleton: GroundCache static get instance() { @@ -88,22 +89,29 @@ export class GroundCache extends PatchesContainer { /** * Query block from cache - * @param blockPos - * @returns + * @param blockPos + * @returns */ - queryBlock(pos: Vector2, { cacheIfMissing, precacheRadius } = { cacheIfMissing: false, precacheRadius: 0 }) { + queryBlock( + pos: Vector2, + { cacheIfMissing, precacheRadius } = { + cacheIfMissing: false, + precacheRadius: 0, + }, + ) { const block = this.findPatch(pos)?.getBlock(pos) let pendingReq if ((!block && cacheIfMissing) || precacheRadius > 0) { - pendingReq = this.precacheAroundPos(pos, precacheRadius) - .then(_ => GroundCache.instance.queryBlock(pos) as PatchBlock) as Promise + pendingReq = this.precacheAroundPos(pos, precacheRadius).then( + () => GroundCache.instance.queryBlock(pos) as PatchBlock, + ) as Promise } return block || pendingReq } rebuildPatchIndex(cacheBounds: Box2) { - const patchKeys = getPatchIds(cacheBounds, this.patchDimensions).map(patchId => - serializePatchId(patchId), + const patchKeys = getPatchIds(cacheBounds, this.patchDimensions).map( + patchId => serializePatchId(patchId), ) const foundOrMissing = patchKeys.map(key => this.patchLookup[key] || key) const changesCount = foundOrMissing.filter( @@ -127,10 +135,7 @@ export class GroundCache extends PatchesContainer { precacheRadius, precacheRadius, ).multiplyScalar(2) - const precache_box = new Box2().setFromCenterAndSize( - center, - precache_dims, - ) + const precache_box = new Box2().setFromCenterAndSize(center, precache_dims) GroundCache.instance.rebuildPatchIndex(precache_box) return GroundCache.instance.loadEmpty(false) } @@ -146,11 +151,10 @@ export class GroundMap extends GroundCache { // this.loadEmpty() // } - override rebuildPatchIndex(mapBounds: Box2) { this.mapBounds = mapBounds - const patchKeys = getPatchIds(mapBounds, this.patchDimensions).map(patchId => - serializePatchId(patchId), + const patchKeys = getPatchIds(mapBounds, this.patchDimensions).map( + patchId => serializePatchId(patchId), ) const foundOrMissing = patchKeys.map(key => this.patchLookup[key] || key) const changesCount = foundOrMissing.filter( diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index 7a557b7..4f3ba0a 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -1,33 +1,17 @@ -import { Box2, MathUtils, Vector2, Vector3 } from 'three' +import { Box2, Vector2, Vector3 } from 'three' -import { Block, PatchBlock, PatchKey } from '../common/types' +import { BlockData, GroundBlock, PatchBlock, PatchKey } from '../common/types' import { parsePatchKey, parseThreeStub, asVect3, asVect2, } from '../common/utils' -import { WorldComputeProxy } from '../index' +import { BlockMode, WorldComputeProxy } from '../index' import { BlockType } from '../procgen/Biome' import { PatchContainer } from './PatchContainer' -export enum BlockMode { - DEFAULT, - BOARD_CONTAINER, -} - -export type GroundRawData = { - rawVal: number - confIndex: number -} - -export type BlockData = { - level: number - type: BlockType - mode?: BlockMode -} - export type PatchStub = { key?: string bounds: Box2 @@ -42,14 +26,14 @@ const BlockDataBitAllocation = { mode: 3, // support for 8 different block mode } -export type BlockIteratorRes = IteratorResult +export type BlockIteratorRes = IteratorResult /** - * field | bits alloc | value range + * field | bits alloc | value range * -----|------------|-------------------------------- - * ground elevation | 10 | 1024 - * groundIndex# | 6 | 64 + * ground elevation | 10 | 1024 + * groundIndex# | 6 | 64 * overgroundIndex | 16 | support for 65536 different configurations - * + * */ export class GroundPatch extends PatchContainer { rawData: Uint32Array @@ -169,15 +153,15 @@ export class GroundPatch extends PatchContainer { // bounds.max.y = Math.max(bounds.max.y, levelMax) } - genGroundBuffer(blockIndex: number, ymin: number, ymax: number) { - const block = this.readBlockData(blockIndex) - let bufferCount = MathUtils.clamp(block.level - ymin, 0, ymax - ymin) - const groundBuffer = []; - while (bufferCount > 0) { - groundBuffer.push(block.type) - } - return groundBuffer - } + // genGroundBuffer(blockIndex: number, ymin: number, ymax: number) { + // const block = this.readBlockData(blockIndex) + // const bufferCount = MathUtils.clamp(block.level - ymin, 0, ymax - ymin) + // const groundBuffer = [] + // while (bufferCount > 0) { + // groundBuffer.push(block.type) + // } + // return groundBuffer + // } /** * diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index cb8bd05..45fdd01 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -2,11 +2,44 @@ import alea from 'alea' import { Box2, Vector2 } from 'three' import { ProcLayer } from '../procgen/ProcLayer' -import { BlueNoisePattern, DistributionParams } from '../procgen/BlueNoisePattern' +import { + BlueNoisePattern, + DistributionParams, +} from '../procgen/BlueNoisePattern' import { getPatchIds } from '../common/utils' import { ItemType } from '../misc/ItemsInventory' import { WorldConf } from '../misc/WorldConfig' +const distDefaults = { + aleaSeed: 'treeMap', + maxDistance: 100, + tries: 20, +} + +export enum DistributionProfile { + SMALL, + MEDIUM, + LARGE, +} + +export const DistributionProfiles: Record< + DistributionProfile, + DistributionParams +> = { + [DistributionProfile.SMALL]: { + ...distDefaults, + minDistance: 4, + }, + [DistributionProfile.MEDIUM]: { + ...distDefaults, + minDistance: 8, + }, + [DistributionProfile.LARGE]: { + ...distDefaults, + minDistance: 16, + }, +} + const probabilityThreshold = Math.pow(2, 8) const bmin = new Vector2(0, 0) const bmax = new Vector2( @@ -26,7 +59,9 @@ export class PseudoDistributionMap { constructor( bounds: Box2 = distMapDefaultBox, - distParams: DistributionParams = DistributionProfiles[DistributionProfile.MEDIUM], + distParams: DistributionParams = DistributionProfiles[ + DistributionProfile.MEDIUM + ], ) { this.patchDimensions = bounds.getSize(new Vector2()) this.repeatedPattern = new BlueNoisePattern(bounds, distParams) @@ -67,11 +102,16 @@ export class PseudoDistributionMap { const patchIds = getPatchIds(queryBox, this.patchDimensions) for (const patchId of patchIds) { const offset = patchId.clone().multiply(this.patchDimensions) - const localRegionQuery = queryBox.clone().translate(offset.clone().negate()) + const localRegionQuery = queryBox + .clone() + .translate(offset.clone().negate()) // look for entities overlapping with input point or area for (const spawnLocalPos of this.repeatedPattern.elements) { // eval spawn probability at entity center - const spawnBox = new Box2().setFromCenterAndSize(spawnLocalPos, itemDims) + const spawnBox = new Box2().setFromCenterAndSize( + spawnLocalPos, + itemDims, + ) if (spawnBox.intersectsBox(localRegionQuery)) { const itemPos = spawnLocalPos.clone().add(offset) spawnLocations.push(itemPos) @@ -81,26 +121,30 @@ export class PseudoDistributionMap { return spawnLocations } - getSpawnedItem(itemPos: Vector2, spawnableItems: ItemType[], spawnProbabilityEval = this.spawnProbabilityEval) { + getSpawnedItem( + itemPos: Vector2, + spawnableItems: ItemType[], + spawnProbabilityEval = this.spawnProbabilityEval, + ) { // const spawnedItems: Record = {} const itemsCount = spawnableItems.length // spawnablePlaces.forEach(itemPos => { - const itemId = itemPos.x + ':' + itemPos.y - const prng = alea(itemId) - const rand = prng() - const hasSpawned = rand * spawnProbabilityEval(itemPos) < probabilityThreshold - if (hasSpawned) { - const itemIndex = Math.round(rand * itemsCount * 10) - const itemKey = spawnableItems[itemIndex % itemsCount] as ItemType - // if (itemKey !== undefined) { - // spawnedItems[itemKey] = spawnedItems[itemKey] || []; - // (spawnedItems[itemKey] as Vector2[]).push(itemPos) - // } - return itemKey - } + const itemId = itemPos.x + ':' + itemPos.y + const prng = alea(itemId) + const rand = prng() + const hasSpawned = + rand * spawnProbabilityEval(itemPos) < probabilityThreshold + if (hasSpawned) { + const itemIndex = Math.round(rand * itemsCount * 10) + const itemKey = spawnableItems[itemIndex % itemsCount] as ItemType + // if (itemKey !== undefined) { + // spawnedItems[itemKey] = spawnedItems[itemKey] || []; + // (spawnedItems[itemKey] as Vector2[]).push(itemPos) + // } + return itemKey + } // }) - - // return spawnedItems + return null } // /** @@ -113,33 +157,6 @@ export class PseudoDistributionMap { // } } -const distDefaults = { - aleaSeed: 'treeMap', - maxDistance: 100, - tries: 20, -} - -export enum DistributionProfile{ - SMALL, - MEDIUM, - LARGE -} - -export const DistributionProfiles: Record = { - [DistributionProfile.SMALL]: { - ...distDefaults, - minDistance: 4 - }, - [DistributionProfile.MEDIUM]: { - ...distDefaults, - minDistance: 8 - }, - [DistributionProfile.LARGE]: { - ...distDefaults, - minDistance: 16 - } -} - /** * Storing entities at biome level with overlap at biomes' transitions */ @@ -159,14 +176,12 @@ export class OverlappingEntitiesMap { // const adjacentMaps = adjacentKeys.map(mapKey => RandomDistributionMap.mapsLookup[mapKey]) // return adjacentEntities // } - // Gen all entities belonging to specific biome // populate(blockPos: Vector3) { // // find biome at given block pos // // discover biome extent // // generate entities over all biome // } - // override *iterate(input: Box3 | Vector3) { // // find if biome cached entities exists for given block or patch // // if not populate biomes cache with entities diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index caa6a1b..ee22944 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -1,6 +1,6 @@ import { Box2, Vector2, Vector3, Vector3Like } from 'three' -import { Block, PatchBlock } from '../common/types' +import { BlockData, GroundBlock, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, @@ -8,10 +8,10 @@ import { GroundPatch, ProcLayer, WorldComputeProxy, + BlockMode, } from '../index' import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' -import { BlockData, BlockMode } from '../datacontainers/GroundPatch' import { ItemType } from '../misc/ItemsInventory' export enum BlockCategory { @@ -87,8 +87,8 @@ export class BoardContainer extends GroundPatch { async make() { await this.fillAndShapeBoard() - const obstacles: Block[] = await this.retrieveAndTrimTrees() - const holes: Block[] = this.getHolesAreasBis(obstacles) + const obstacles = await this.retrieveAndTrimTrees() + const holes = this.getHolesAreasBis(obstacles) holes.forEach(block => this.digGroundHole(block)) } @@ -148,20 +148,14 @@ export class BoardContainer extends GroundPatch { async retrieveAndTrimTrees() { // request all entities belonging to the board - const items: Record = await WorldComputeProxy.instance.queryOvergroundItems( - this.bounds, - ) + const items: Record = + await WorldComputeProxy.instance.queryOvergroundItems(this.bounds) const boardItems = [] - const itemsChunks = [] - for (const [itemType, spawnInstances] of Object.entries(items)) { - const withinBoardItems = spawnInstances.filter(spawnOrigin => this.isWithinBoard(spawnOrigin)) + for (const [, spawnInstances] of Object.entries(items)) { + const withinBoardItems = spawnInstances.filter(spawnOrigin => + this.isWithinBoard(spawnOrigin), + ) for await (const itemPos of withinBoardItems) { - // const itemChunk = await ItemsInventory.getItemChunkInstance(itemType, itemPos) - // trim chunk - // itemChunk?.bounds.min.y= - // itemChunk?.bounds.max.y= - // itemsChunks.push(itemChunk) - const boardBlock = this.getBlock(itemPos) if (boardBlock) { boardBlock.pos.y += 1 @@ -181,14 +175,12 @@ export class BoardContainer extends GroundPatch { queryLocalEntities( boardContainer: GroundPatch, distMap: PseudoDistributionMap, - entityRadius = 2, + itemRadius = 2, ) { - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => - testRange.distanceToPoint(entityPos) <= entityRadius + const itemDims = new Vector2(itemRadius, itemRadius) const spawnLocs = distMap.querySpawnLocations( boardContainer.bounds, - intersectsEntity, - () => 1, + itemDims, ) const entities = spawnLocs .map(loc => { @@ -205,7 +197,7 @@ export class BoardContainer extends GroundPatch { return BoardContainer.holesMapDistribution.eval(testPos) < 0.15 } - digGroundHole(holeBlock: Block) { + digGroundHole(holeBlock: GroundBlock) { holeBlock.data.type = BlockType.HOLE holeBlock.data.level -= 1 // dig hole in the ground holeBlock.data.mode = BlockMode.DEFAULT @@ -220,7 +212,7 @@ export class BoardContainer extends GroundPatch { return holesSingleBlocks } - getHolesAreas(boardContainer: GroundPatch, forbiddenBlocks: Block[]) { + getHolesAreas(boardContainer: GroundPatch, forbiddenBlocks: GroundBlock[]) { const forbiddenPos = forbiddenBlocks.map(({ pos }) => asVect2(pos)) const holesMono = this.queryLocalEntities( boardContainer, @@ -240,15 +232,15 @@ export class BoardContainer extends GroundPatch { holesMulti.push(block) } }) - return holesMulti.map(({ pos, data }) => ({ pos, data }) as Block) + return holesMulti.map(({ pos, data }) => ({ pos, data }) as GroundBlock) } - getHolesAreasBis(forbiddenBlocks: Block[]) { + getHolesAreasBis(forbiddenBlocks: any[]) { // prevent holes from spreading over forbidden blocks const isForbiddenPos = (testPos: Vector3) => !!forbiddenBlocks.find(block => block.pos.equals(testPos)) const blocks = this.iterBlocksQuery() - const holes: Block[] = [] + const holes: PatchBlock[] = [] for (const block of blocks) { const testPos = block.pos if ( @@ -259,7 +251,7 @@ export class BoardContainer extends GroundPatch { holes.push(block) } } - return holes.map(({ pos, data }) => ({ pos, data }) as Block) + return holes.map(({ pos, data }) => ({ pos, data }) as GroundBlock) } /** diff --git a/src/index.ts b/src/index.ts index b173bc2..0c6e8b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,17 +3,26 @@ export { WorldConf } from './misc/WorldConfig' export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { PseudoDistributionMap, DistributionProfile } from './datacontainers/RandomDistributionMap' -export { BoardContainer } from './feats/BoardContainer' -export { BlockMode, GroundPatch } from './datacontainers/GroundPatch' +export { + PseudoDistributionMap, + DistributionProfile, +} from './datacontainers/RandomDistributionMap' +export { GroundPatch } from './datacontainers/GroundPatch' // export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundMap' export { GroundCache, GroundMap } from './datacontainers/GroundCache' +export { BoardContainer } from './feats/BoardContainer' export { ChunkFactory } from './tools/ChunkFactory' export { WorldComputeProxy } from './api/WorldComputeProxy' export { PatchContainer } from './datacontainers/PatchContainer' export { ItemsInventory } from './misc/ItemsInventory' export { SchematicLoader } from './tools/SchematicLoader' -export { ProceduralItemGenerator, ProcItemType, ProcItemCategory } from './tools/ProceduralGenerators' +export { + ProceduralItemGenerator, + ProcItemType, + ProcItemCategory, +} from './tools/ProceduralGenerators' +export { BlockMode } from './common/types' + export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' -// export * as ProceduralGenerators from './tools/ProceduralGenerators' \ No newline at end of file +// export * as ProceduralGenerators from './tools/ProceduralGenerators' diff --git a/src/misc/ItemsInventory.ts b/src/misc/ItemsInventory.ts index 9e4bebd..065d482 100644 --- a/src/misc/ItemsInventory.ts +++ b/src/misc/ItemsInventory.ts @@ -1,7 +1,11 @@ -import { Box3, Vector3 } from "three" -import { ChunkContainer } from "../datacontainers/ChunkContainer" -import { ProceduralItemGenerator, ProcItemConf } from "../tools/ProceduralGenerators" -import { SchematicLoader } from "../tools/SchematicLoader" +import { Box3, Vector3 } from 'three' + +import { ChunkContainer } from '../datacontainers/ChunkContainer' +import { + ProceduralItemGenerator, + ProcItemConf, +} from '../tools/ProceduralGenerators' +import { SchematicLoader } from '../tools/SchematicLoader' export type ItemType = string @@ -10,63 +14,71 @@ export type ItemType = string */ export class ItemsInventory { - static externalResources: { - procItemsConfigs: Record - schemFileUrls: Record - } = { - procItemsConfigs: {}, - schemFileUrls: {} - } - static catalog: Record = {} - // static spawners: Record = {} - /** - * Populate from schematics - * @param schematicFileUrls - * @param optionalDataEncoder - */ - static async importSchematic(id: ItemType) { - const fileUrl = this.externalResources.schemFileUrls[id] - let chunk - if (fileUrl) { - chunk = await SchematicLoader.createChunkContainer(fileUrl) - // const spawner = new PseudoDistributionMap() - ItemsInventory.catalog[id] = chunk - } - return chunk - } + static externalResources: { + procItemsConfigs: Record + schemFileUrls: Record + } = { + procItemsConfigs: {}, + schemFileUrls: {}, + } - static importProcItem(id: ItemType) { - const procConf = this.externalResources.procItemsConfigs[id] - let chunk - if (procConf) { - chunk = ProceduralItemGenerator.voxelizeItem(procConf.category, procConf.params) - // const spawner = new PseudoDistributionMap() - if (chunk) { - ItemsInventory.catalog[id] = chunk - } - } - return chunk + static catalog: Record = {} + // static spawners: Record = {} + /** + * Populate from schematics + * @param schematicFileUrls + * @param optionalDataEncoder + */ + static async importSchematic(id: ItemType) { + const fileUrl = this.externalResources.schemFileUrls[id] + let chunk + if (fileUrl) { + chunk = await SchematicLoader.createChunkContainer(fileUrl) + // const spawner = new PseudoDistributionMap() + ItemsInventory.catalog[id] = chunk } + return chunk + } - static async getTemplateChunk(itemId: string) { - return this.catalog[itemId] || await this.importSchematic(itemId) || this.importProcItem(itemId) + static importProcItem(id: ItemType) { + const procConf = this.externalResources.procItemsConfigs[id] + let chunk + if (procConf) { + chunk = ProceduralItemGenerator.voxelizeItem( + procConf.category, + procConf.params, + ) + // const spawner = new PseudoDistributionMap() + if (chunk) { + ItemsInventory.catalog[id] = chunk + } } + return chunk + } + + static async getTemplateChunk(itemId: string) { + return ( + this.catalog[itemId] || + (await this.importSchematic(itemId)) || + this.importProcItem(itemId) + ) + } - static async getInstancedChunk(itemType: ItemType, itemPos: Vector3) { - let itemChunk: ChunkContainer | undefined - const templateChunk = await this.getTemplateChunk(itemType) - if (templateChunk) { - const dims = templateChunk.bounds.getSize(new Vector3()) - // const translation = parseThreeStub(spawnLoc).sub(new Vector3(dims.x / 2, 0, dims.z / 2).round()) - // const entityBounds = entity.template.bounds.clone().translate(translation) - const entityBounds = new Box3().setFromCenterAndSize(itemPos, dims) - entityBounds.min.y = itemPos.y - entityBounds.max.y = itemPos.y + dims.y - entityBounds.min.floor() - entityBounds.max.floor() - itemChunk = new ChunkContainer(entityBounds, 0) - itemChunk.rawData.set(templateChunk.rawData) - } - return itemChunk + static async getInstancedChunk(itemType: ItemType, itemPos: Vector3) { + let itemChunk: ChunkContainer | undefined + const templateChunk = await this.getTemplateChunk(itemType) + if (templateChunk) { + const dims = templateChunk.bounds.getSize(new Vector3()) + // const translation = parseThreeStub(spawnLoc).sub(new Vector3(dims.x / 2, 0, dims.z / 2).round()) + // const entityBounds = entity.template.bounds.clone().translate(translation) + const entityBounds = new Box3().setFromCenterAndSize(itemPos, dims) + entityBounds.min.y = itemPos.y + entityBounds.max.y = itemPos.y + dims.y + entityBounds.min.floor() + entityBounds.max.floor() + itemChunk = new ChunkContainer(entityBounds, 0) + itemChunk.rawData.set(templateChunk.rawData) } -} \ No newline at end of file + return itemChunk + } +} diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index ba8580b..c99c044 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -24,8 +24,9 @@ export class WorldConf { static defaultDistMapPeriod = 4 * WorldConf.patchSize static settings = { - useBiomeBilinearInterpolation: true + useBiomeBilinearInterpolation: true, } + static debug = { patch: { borderHighlightColor: BlockType.NONE, @@ -34,8 +35,8 @@ export class WorldConf { startPosHighlightColor: BlockType.NONE, splitSidesColoring: false, }, - schematics:{ - missingBlockType: BlockType.NONE - } + schematics: { + missingBlockType: BlockType.NONE, + }, } } diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 5bbadd3..c93ff72 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -1,52 +1,59 @@ import { Vector2, Vector3 } from 'three' - // import { MappingProfiles, ProfilePreset } from "../tools/MappingPresets" +import { smoothstep } from 'three/src/math/MathUtils' + import { LinkedList } from '../common/misc' import { MappingRangeSorter } from '../common/utils' import * as Utils from '../common/utils' +import { + BiomeLandscapeKey, + BiomesConf, + BiomesRawConf, + LandscapeFields, + LandscapesConf, +} from '../common/types' import { ProcLayer } from './ProcLayer' -import { BiomeConfigs, BiomeLandscapeElement, BiomeLandscapeKey } from '../common/types' -import { smoothstep } from 'three/src/math/MathUtils' // reserved native block types export enum BlockType { NONE, + MUD, TRUNK, FOLIAGE_LIGHT, FOLIAGE_DARK, HOLE, - LAST_PLACEHOLDER + LAST_PLACEHOLDER, } enum Level { LOW = 'low', MID = 'mid', - HIGH = 'high' + HIGH = 'high', } enum HeatLevel { COLD = 'cold', TEMPERATE = 'temperate', - HOT = 'hot' + HOT = 'hot', } enum RainLevel { DRY = 'dry', MODERATE = 'mod', - WET = 'wet' + WET = 'wet', } const heatLevelMappings: Record = { [Level.LOW]: HeatLevel.COLD, [Level.MID]: HeatLevel.TEMPERATE, - [Level.HIGH]: HeatLevel.HOT + [Level.HIGH]: HeatLevel.HOT, } const rainLevelMappings: Record = { [Level.LOW]: RainLevel.DRY, [Level.MID]: RainLevel.MODERATE, - [Level.HIGH]: RainLevel.WET + [Level.HIGH]: RainLevel.WET, } export enum BiomeType { @@ -58,7 +65,10 @@ export enum BiomeType { type Contribution = Record -const translateContribution = (contribution: Contribution, keyMapping: Record) => { +const translateContribution = ( + contribution: Contribution, + keyMapping: Record, +) => { const mappedContribution: Record = {} as Record Object.entries(contribution).forEach(([key, val]) => { const targetKey = keyMapping[key as Level] as T @@ -100,7 +110,7 @@ export class Biome { // heatProfile: MappingRanges // rainProfile: MappingRanges - mappings = {} as BiomeConfigs + mappings = {} as BiomesConf posRandomizer: ProcLayer /** * val < lowToMid=> LOW = 1 @@ -120,9 +130,9 @@ export class Biome { seaLevel: 0, } - indexedConf = new Map + indexedConf = new Map() - constructor(biomeConf?: BiomeConfigs) { + constructor(biomeRawConf?: BiomesRawConf) { this.heatmap = new ProcLayer('heatmap') this.heatmap.sampling.harmonicsCount = 6 this.heatmap.sampling.periodicity = 8 @@ -134,7 +144,7 @@ export class Biome { // this.rainProfile = LinkedList.fromArrayAfterSorting(mappingProfile, MappingRangeSorter) // 3 levels (DRY, MODERATE, WET) this.posRandomizer = new ProcLayer('pos_random') this.posRandomizer.sampling.periodicity = 6 - if (biomeConf) this.parseBiomesConfig(biomeConf) + if (biomeRawConf) this.parseBiomesConfig(biomeRawConf) } static get instance() { @@ -143,8 +153,8 @@ export class Biome { } getConfIndex(confKey: BiomeLandscapeKey) { - const confKeys = [...this.indexedConf.keys()]; // Spread keys into an array - const confIndex = confKeys.indexOf(confKey); // Find the index of 'key2' + const confKeys = [...this.indexedConf.keys()] // Spread keys into an array + const confIndex = confKeys.indexOf(confKey) // Find the index of 'key2' return confIndex } @@ -153,12 +163,12 @@ export class Biome { * @param input either blocks position, or pre-requested biome contributions * @returns */ - getBiomeType(input: Vector3 | BiomeInfluence): BiomeType { + getBiomeType(input: Vector3 | BiomeInfluence) { const biomeContribs = input instanceof Vector3 ? this.getBiomeInfluence(input) : input const mainBiome = Object.entries(biomeContribs).sort( (a, b) => b[1] - a[1], - )[0]?.[0] + )[0]?.[0] as string return mainBiome as BiomeType } @@ -169,31 +179,31 @@ export class Biome { low: 0, mid: 0, high: 0, - }; + } // LOW if (value < steps.lowToMid) { - contributions.low = 1; + contributions.low = 1 } // dec LOW, inc MID else if (value < steps.mid) { - const interp = smoothstep(value, steps.lowToMid, steps.mid); - contributions.low = 1 - interp; - contributions.mid = interp; + const interp = smoothstep(value, steps.lowToMid, steps.mid) + contributions.low = 1 - interp + contributions.mid = interp } // MID else if (value < steps.midToHigh) { - contributions.mid = 1; + contributions.mid = 1 } // dec MID/ inc HIGH else if (value < steps.high) { - const interp = smoothstep(value, steps.midToHigh, steps.high); - contributions.mid = 1 - interp; - contributions.high = interp; + const interp = smoothstep(value, steps.midToHigh, steps.high) + contributions.mid = 1 - interp + contributions.high = interp } // HIGH else { - contributions.high = 1; + contributions.high = 1 } // if (value < 0.5) { @@ -206,7 +216,7 @@ export class Biome { // contributions.high = heatLevel // } - return contributions; + return contributions } getBiomeInfluence(pos: Vector2 | Vector3): BiomeInfluence { @@ -223,7 +233,6 @@ export class Biome { contrib = this.calculateContributions(rainVal) const rainContributions = translateContribution(contrib, rainLevelMappings) - Object.entries(heatContributions).forEach(([k1, v1]) => { Object.entries(rainContributions).forEach(([k2, v2]) => { const biomeType = BiomesMapping[k1 as HeatLevel][k2 as RainLevel] @@ -232,10 +241,10 @@ export class Biome { }) Object.keys(biomeContribs).forEach( k => - (biomeContribs[k as BiomeType] = Utils.roundToDec( - biomeContribs[k as BiomeType], - 2, - )), + (biomeContribs[k as BiomeType] = Utils.roundToDec( + biomeContribs[k as BiomeType], + 2, + )), ) // biomeContribs[BiomeType.Artic] = 1 @@ -244,17 +253,15 @@ export class Biome { return biomeContribs } - parseBiomesConfig(biomeConfigs: BiomeConfigs) { + parseBiomesConfig(biomesRawConf: BiomesRawConf) { // Object.entries(biomeConfigs).forEach(([biomeType, biomeConf]) => { // complete missing data - for (const item of Object.entries(biomeConfigs)) { - const [biomeType, biomeConf] = item - for (const subItem of Object.entries(biomeConf)) { - const [confId, confData] = subItem - confData.key = biomeType + '_' + confId + for (const [biomeType, biomeConf] of Object.entries(biomesRawConf)) { + for (const [landId, landConf] of Object.entries(biomeConf)) { + landConf.key = biomeType + '_' + landId } - const configItems = Object.values(biomeConf) + const configItems = Object.values(biomeConf) as LandscapeFields[] const mappingRanges = LinkedList.fromArrayAfterSorting( configItems, MappingRangeSorter, @@ -272,42 +279,42 @@ export class Biome { landscapeTransition = ( groundPos: Vector2, baseHeight: number, - blockMapping: BiomeLandscapeElement, + landscapeConf: LandscapesConf, ) => { const period = 0.005 * Math.pow(2, 2) const mapCoords = groundPos.clone().multiplyScalar(period) const posRandomizerVal = this.posRandomizer.eval(mapCoords) // add some height variations to break painting monotony - const { amplitude }: any = blockMapping.data + const { amplitude }: any = landscapeConf.data const bounds = { - lower: blockMapping.data.x, - upper: blockMapping.next?.data.x || 1, + lower: landscapeConf.data.x, + upper: landscapeConf.next?.data.x || 1, } let blockType // randomize on lower side if ( - blockMapping.prev && + landscapeConf.prev && baseHeight - bounds.lower <= bounds.upper - baseHeight && baseHeight - amplitude.low < bounds.lower ) { const heightVariation = posRandomizerVal * amplitude.low const varyingHeight = baseHeight - heightVariation blockType = - varyingHeight < blockMapping.data.x - ? blockMapping.prev?.data.type - : blockMapping.data.type + varyingHeight < landscapeConf.data.x + ? landscapeConf.prev?.data.type + : landscapeConf.data.type } // randomize on upper side - else if (blockMapping.next && baseHeight + amplitude.high > bounds.upper) { + else if (landscapeConf.next && baseHeight + amplitude.high > bounds.upper) { // let heightVariation = // Utils.clamp(this.paintingRandomness.eval(groundPos), 0.5, 1) * randomness.high // heightVariation = heightVariation > 0 ? (heightVariation - 0.5) * 2 : 0 const heightVariation = posRandomizerVal * amplitude.high const varyingHeight = baseHeight + heightVariation blockType = - varyingHeight > blockMapping.next.data.x - ? blockMapping.next.data.type - : blockMapping.data.type + varyingHeight > landscapeConf.next.data.x + ? landscapeConf.next.data.type + : landscapeConf.data.type } return blockType } @@ -320,13 +327,11 @@ export class Biome { const { seaLevel } = this.params rawVal = includeSea ? Math.max(rawVal, seaLevel) : rawVal rawVal = Utils.clamp(rawVal, 0, 1) - const mappingRange = Utils.findMatchingRange( - rawVal, - this.mappings[biomeType], - ) - const upperRange = mappingRange.next || mappingRange - const min = new Vector2(mappingRange.data.x, mappingRange.data.y) - const max = new Vector2(upperRange.data.x, upperRange.data.y) + const biomeConf = this.mappings[biomeType] + const current = Utils.findMatchingRange(rawVal, biomeConf) + const upper = current?.next || current + const min = new Vector2(current.data.x, current.data.y) + const max = new Vector2(upper.data.x, upper.data.y) const lerp = min.lerp(max, (rawVal - min.x) / (max.x - min.x)) return lerp.y // includeSea ? Math.max(interpolated, seaLevel) : interpolated } @@ -345,18 +350,12 @@ export class Biome { } getBiomeConf = (rawVal: number, biomeType: BiomeType) => { - // nominal block type - let mappingRange = Utils.findMatchingRange( - rawVal as number, - this.mappings[biomeType], - ) - while (!mappingRange.data.type && mappingRange.prev) { - mappingRange = mappingRange.prev - } + const firstItem = this.mappings[biomeType] + let currentItem = Utils.findMatchingRange(rawVal as number, firstItem) - const biomeConfKey = mappingRange.data.key - // const finalBlockType = this.blockRandomization(groundPos, baseHeight, currentBlockMap) - // if (finalBlockType !== nominalBlockType) console.log(`[getBlockType] nominal${nominalBlockType} random${finalBlock}`) - return this.indexedConf.get(biomeConfKey) + while (!currentItem?.data.type && currentItem?.prev) { + currentItem = currentItem.prev + } + return currentItem } } diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index baba269..a234c3b 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -6,7 +6,7 @@ export type DistributionParams = { minDistance: number maxDistance?: number tries?: number - distanceFunction?: ((point: any) => number) + distanceFunction?: (point: any) => number bias?: number aleaSeed?: string } diff --git a/src/procgen/Heightmap.ts b/src/procgen/Heightmap.ts index 525d0b5..fd83228 100644 --- a/src/procgen/Heightmap.ts +++ b/src/procgen/Heightmap.ts @@ -71,7 +71,8 @@ export class Heightmap { // includeSea?: boolean, ) { rawVal = rawVal || this.getRawVal(blockPos) - biomeInfluence = biomeInfluence || Biome.instance.getBiomeInfluence(blockPos) + biomeInfluence = + biomeInfluence || Biome.instance.getBiomeInfluence(blockPos) // (blockData as BlockIterData).cache.type = Biome.instance.getBlockType(blockPos, noiseVal) // noiseVal = includeSea ? Math.max(noiseVal, Biome.instance.params.seaLevel) : noiseVal const initialVal = Biome.instance.getBlockLevelInterpolated( diff --git a/src/third-party/nbt_custom.ts b/src/third-party/nbt_custom.ts index f4fc6b2..a8b2e24 100644 --- a/src/third-party/nbt_custom.ts +++ b/src/third-party/nbt_custom.ts @@ -1,283 +1,299 @@ /** * Customized and refactored code originating from - * - * NBT.js - a JavaScript parser for NBT archives - * by Sijmen Mulder - * + * + * NBT.js - a JavaScript parser for NBT archives + * by Sijmen Mulder + * */ // var zlib = typeof require !== 'undefined' ? require('zlib') : window.zlib; function hasGzipHeader(data: any) { - var head = new Uint8Array(data.slice(0, 2)); - return head.length === 2 && head[0] === 0x1f && head[1] === 0x8b; + const head = new Uint8Array(data.slice(0, 2)) + return head.length === 2 && head[0] === 0x1f && head[1] === 0x8b } function decodeUTF8(array: any[]) { - var codepoints = [], i; - for (i = 0; i < array.length; i++) { - if ((array[i] & 0x80) === 0) { - codepoints.push(array[i] & 0x7F); - } else if (i + 1 < array.length && - (array[i] & 0xE0) === 0xC0 && - (array[i + 1] & 0xC0) === 0x80) { - codepoints.push( - ((array[i] & 0x1F) << 6) | - (array[i + 1] & 0x3F)); - } else if (i + 2 < array.length && - (array[i] & 0xF0) === 0xE0 && - (array[i + 1] & 0xC0) === 0x80 && - (array[i + 2] & 0xC0) === 0x80) { - codepoints.push( - ((array[i] & 0x0F) << 12) | - ((array[i + 1] & 0x3F) << 6) | - (array[i + 2] & 0x3F)); - } else if (i + 3 < array.length && - (array[i] & 0xF8) === 0xF0 && - (array[i + 1] & 0xC0) === 0x80 && - (array[i + 2] & 0xC0) === 0x80 && - (array[i + 3] & 0xC0) === 0x80) { - codepoints.push( - ((array[i] & 0x07) << 18) | - ((array[i + 1] & 0x3F) << 12) | - ((array[i + 2] & 0x3F) << 6) | - (array[i + 3] & 0x3F)); - } - } - return String.fromCharCode.apply(null, codepoints); + const codepoints = [] + let i + for (i = 0; i < array.length; i++) { + if ((array[i] & 0x80) === 0) { + codepoints.push(array[i] & 0x7f) + } else if ( + i + 1 < array.length && + (array[i] & 0xe0) === 0xc0 && + (array[i + 1] & 0xc0) === 0x80 + ) { + codepoints.push(((array[i] & 0x1f) << 6) | (array[i + 1] & 0x3f)) + } else if ( + i + 2 < array.length && + (array[i] & 0xf0) === 0xe0 && + (array[i + 1] & 0xc0) === 0x80 && + (array[i + 2] & 0xc0) === 0x80 + ) { + codepoints.push( + ((array[i] & 0x0f) << 12) | + ((array[i + 1] & 0x3f) << 6) | + (array[i + 2] & 0x3f), + ) + } else if ( + i + 3 < array.length && + (array[i] & 0xf8) === 0xf0 && + (array[i + 1] & 0xc0) === 0x80 && + (array[i + 2] & 0xc0) === 0x80 && + (array[i + 3] & 0xc0) === 0x80 + ) { + codepoints.push( + ((array[i] & 0x07) << 18) | + ((array[i + 1] & 0x3f) << 12) | + ((array[i + 2] & 0x3f) << 6) | + (array[i + 3] & 0x3f), + ) + } + } + return String.fromCharCode.apply(null, codepoints) } function sliceUint8Array(array: Uint8Array, begin: number, end: number) { - if ('slice' in array) { - return array.slice(begin, end); - } else { - return new Uint8Array([].slice.call(array, begin, end)); - } + if ('slice' in array) { + return array.slice(begin, end) + } else { + return new Uint8Array([].slice.call(array, begin, end)) + } } /** -* A mapping from enum to NBT type numbers. -* -* @type Object -* @see module:nbt.tagTypeNames -*/ + * A mapping from enum to NBT type numbers. + * + * @type Object + * @see module:nbt.tagTypeNames + */ enum DataType { - END, - BYTE, - SHORT, - INT, - LONG, - FLOAT, - DOUBLE, - BYTE_ARRAY, - STRING, - LIST, - COMPOUND, - INT_ARRAY, - LONG_ARRAY + END, + BYTE, + SHORT, + INT, + LONG, + FLOAT, + DOUBLE, + BYTE_ARRAY, + STRING, + LIST, + COMPOUND, + INT_ARRAY, + LONG_ARRAY, } const DataTypeNames: Record = { - [DataType.END]: "end", - [DataType.BYTE]: "byte", - [DataType.SHORT]: "short", - [DataType.INT]: "int", - [DataType.LONG]: "long", - [DataType.FLOAT]: "float", - [DataType.DOUBLE]: "double", - [DataType.BYTE_ARRAY]: "byteArray", - [DataType.STRING]: "string", - [DataType.LIST]: "list", - [DataType.COMPOUND]: "compound", - [DataType.INT_ARRAY]: "intArray", - [DataType.LONG_ARRAY]: "longArray" + [DataType.END]: 'end', + [DataType.BYTE]: 'byte', + [DataType.SHORT]: 'short', + [DataType.INT]: 'int', + [DataType.LONG]: 'long', + [DataType.FLOAT]: 'float', + [DataType.DOUBLE]: 'double', + [DataType.BYTE_ARRAY]: 'byteArray', + [DataType.STRING]: 'string', + [DataType.LIST]: 'list', + [DataType.COMPOUND]: 'compound', + [DataType.INT_ARRAY]: 'intArray', + [DataType.LONG_ARRAY]: 'longArray', } /** - * A mapping from NBT type numbers to type names. - * - * @type Object - * @see module:nbt.tagTypes - **/ + * A mapping from NBT type numbers to type names. + * + * @type Object + * @see module:nbt.tagTypes + **/ const DataTypeMapping: Partial> = { - [DataType.BYTE]: "Int8", - [DataType.SHORT]: "Int16", - [DataType.INT]: "Int32", - [DataType.FLOAT]: "Float32", - [DataType.DOUBLE]: "Float64", + [DataType.BYTE]: 'Int8', + [DataType.SHORT]: 'Int16', + [DataType.INT]: 'Int32', + [DataType.FLOAT]: 'Float32', + [DataType.DOUBLE]: 'Float64', } const DataSizeMapping: Partial> = { - [DataType.BYTE]: 1, - [DataType.SHORT]: 2, - [DataType.INT]: 4, - [DataType.FLOAT]: 4, - [DataType.DOUBLE]: 8, + [DataType.BYTE]: 1, + [DataType.SHORT]: 2, + [DataType.INT]: 4, + [DataType.FLOAT]: 4, + [DataType.DOUBLE]: 8, } export class NBTReader { - offset = 0; - arrayView - dataView - dataTypeHandler: Record any> = { - [DataType.END]: function (): void { - throw new Error("Function not implemented."); - }, - [DataType.BYTE]: () => { - return this.read(DataType.BYTE) - }, - [DataType.SHORT]: () => { - return this.read(DataType.SHORT) - }, - [DataType.INT]: () => { - return this.read(DataType.INT) - }, - [DataType.FLOAT]: () => { - return this.read(DataType.FLOAT) - }, - [DataType.DOUBLE]: () => { - return this.read(DataType.DOUBLE) - }, - [DataType.LONG]: () => { - return [this.read(DataType.INT), this.read(DataType.INT)]; - }, - [DataType.BYTE_ARRAY]: () => { - const length = this.read(DataType.INT); - const bytes = []; - for (let i = 0; i < length; i++) { - bytes.push(this.read(DataType.BYTE)); - } - return bytes; - }, - [DataType.STRING]: () => { - const length = this.read(DataType.SHORT); - const slice = sliceUint8Array(this.arrayView, this.offset, - this.offset + length); - this.offset += length; - return decodeUTF8(slice as any); - }, - [DataType.LIST]: () => { - const type = this.read(DataType.BYTE) as DataType; - const length = this.read(DataType.INT); - const values = []; - for (let i = 0; i < length; i++) { - values.push(this.dataTypeHandler[type]()); - } - return { type: DataTypeMapping[type], value: values }; - }, - [DataType.COMPOUND]: () => { - const values: any = {}; - while (true) { - const type = this.read(DataType.BYTE) as DataType; - if (type === DataType.END) { - break; - } - const name = this.dataTypeHandler[DataType.STRING](); - const value = this.dataTypeHandler[type](); - values[name] = { type: DataTypeNames[type], value: value }; - } - return values; - }, - [DataType.INT_ARRAY]: () => { - const length = this.read(DataType.INT); - const ints = []; - for (let i = 0; i < length; i++) { - ints.push(this.read(DataType.INT)); - } - return ints; - }, - [DataType.LONG_ARRAY]: () => { - const length = this.read(DataType.INT); - const longs = []; - for (let i = 0; i < length; i++) { - longs.push(this.read(DataType.LONG)); - } - return longs; - } - } - - constructor(buffer: Iterable) { - // this.buffer = buffer - this.arrayView = new Uint8Array(buffer) - this.dataView = new DataView(this.arrayView.buffer) - } + offset = 0 + arrayView + dataView + dataTypeHandler: Record any> = { + [DataType.END]: function (): void { + throw new Error('Function not implemented.') + }, + [DataType.BYTE]: () => { + return this.read(DataType.BYTE) + }, + [DataType.SHORT]: () => { + return this.read(DataType.SHORT) + }, + [DataType.INT]: () => { + return this.read(DataType.INT) + }, + [DataType.FLOAT]: () => { + return this.read(DataType.FLOAT) + }, + [DataType.DOUBLE]: () => { + return this.read(DataType.DOUBLE) + }, + [DataType.LONG]: () => { + return [this.read(DataType.INT), this.read(DataType.INT)] + }, + [DataType.BYTE_ARRAY]: () => { + const length = this.read(DataType.INT) + const bytes = [] + for (let i = 0; i < length; i++) { + bytes.push(this.read(DataType.BYTE)) + } + return bytes + }, + [DataType.STRING]: () => { + const length = this.read(DataType.SHORT) + const slice = sliceUint8Array( + this.arrayView, + this.offset, + this.offset + length, + ) + this.offset += length + return decodeUTF8(slice as any) + }, + [DataType.LIST]: () => { + const type = this.read(DataType.BYTE) as DataType + const length = this.read(DataType.INT) + const values = [] + for (let i = 0; i < length; i++) { + values.push(this.dataTypeHandler[type]()) + } + return { type: DataTypeMapping[type], value: values } + }, + [DataType.COMPOUND]: () => { + const values: any = {} + while (true) { + const type = this.read(DataType.BYTE) as DataType + if (type === DataType.END) { + break + } + const name = this.dataTypeHandler[DataType.STRING]() + const value = this.dataTypeHandler[type]() + values[name] = { type: DataTypeNames[type], value } + } + return values + }, + [DataType.INT_ARRAY]: () => { + const length = this.read(DataType.INT) + const ints = [] + for (let i = 0; i < length; i++) { + ints.push(this.read(DataType.INT)) + } + return ints + }, + [DataType.LONG_ARRAY]: () => { + const length = this.read(DataType.INT) + const longs = [] + for (let i = 0; i < length; i++) { + longs.push(this.read(DataType.LONG)) + } + return longs + }, + } - read(dataType: DataType) { - const dataSize = DataSizeMapping[dataType] || 0 - const callee = 'get' + DataTypeMapping[dataType] - var val = dataType !== DataType.END ? (this.dataView as any)[callee](this.offset) : ''; - this.offset += dataSize; - return val; - } + constructor(buffer: Iterable) { + // this.buffer = buffer + this.arrayView = new Uint8Array(buffer) + this.dataView = new DataView(this.arrayView.buffer) + } - /** - * @param {ArrayBuffer|Buffer} data - an uncompressed NBT archive - * @returns {{name: string, value: Object.}} - * a named compound - * - * @see module:nbt.parse - * @see module:nbt.writeUncompressed - * - * @example - * nbt.readUncompressed(buf); - * // -> { name: 'My Level', - * // value: { foo: { type: int, value: 42 }, - * // bar: { type: string, value: 'Hi!' }}} */ - static parseUncompressed(data: Iterable) { - if (!data) { throw new Error('Argument "data" is falsy'); } + read(dataType: DataType) { + const dataSize = DataSizeMapping[dataType] || 0 + const callee = 'get' + DataTypeMapping[dataType] + const val = + dataType !== DataType.END + ? (this.dataView as any)[callee](this.offset) + : '' + this.offset += dataSize + return val + } - var reader = new NBTReader(data) + /** + * @param {ArrayBuffer|Buffer} data - an uncompressed NBT archive + * @returns {{name: string, value: Object.}} + * a named compound + * + * @see module:nbt.parse + * @see module:nbt.writeUncompressed + * + * @example + * nbt.readUncompressed(buf); + * // -> { name: 'My Level', + * // value: { foo: { type: int, value: 42 }, + * // bar: { type: string, value: 'Hi!' }}} */ + static parseUncompressed(data: Iterable) { + if (!data) { + throw new Error('Argument "data" is falsy') + } - // var type = reader.byte(); - var type = reader.dataTypeHandler[DataType.BYTE]() - if (type !== DataType.COMPOUND) { - throw new Error('Top tag should be a compound'); - } + const reader = new NBTReader(data) - return { - name: reader.dataTypeHandler[DataType.STRING](), - value: reader.dataTypeHandler[DataType.COMPOUND]() - }; - }; + // var type = reader.byte(); + const type = reader.dataTypeHandler[DataType.BYTE]() + if (type !== DataType.COMPOUND) { + throw new Error('Top tag should be a compound') + } - /** - * @callback parseCallback - * @param {Object} error - * @param {Object} result - a named compound - * @param {string} result.name - the top-level name - * @param {Object} result.value - the top-level compound */ + return { + name: reader.dataTypeHandler[DataType.STRING](), + value: reader.dataTypeHandler[DataType.COMPOUND](), + } + } - /** - * This accepts both gzipped and uncompressd NBT archives. - * If the archive is uncompressed, the callback will be - * called directly from this method. For gzipped files, the - * callback is async. - * - * For use in the browser, window.zlib must be defined to decode - * compressed archives. It will be passed a Buffer if the type is - * available, or an Uint8Array otherwise. - * - * @param {ArrayBuffer|Buffer} data - gzipped or uncompressed data - * @param {parseCallback} callback - * - * @see module:nbt.parseUncompressed - * @see module:nbt.Reader#compound - * - * @example - * nbt.parse(buf, function(error, results) { - * if (error) { - * throw error; - * } - * console.log(result.name); - * console.log(result.value.foo); - * }); */ - static parse(data: any, callback: any) { + /** + * @callback parseCallback + * @param {Object} error + * @param {Object} result - a named compound + * @param {string} result.name - the top-level name + * @param {Object} result.value - the top-level compound */ - if (!hasGzipHeader(data)) { - callback(null, NBTReader.parseUncompressed(data)); - } else { - callback(new Error('NBT compressed archive support is not implemented '), null); - } - }; - -} \ No newline at end of file + /** + * This accepts both gzipped and uncompressd NBT archives. + * If the archive is uncompressed, the callback will be + * called directly from this method. For gzipped files, the + * callback is async. + * + * For use in the browser, window.zlib must be defined to decode + * compressed archives. It will be passed a Buffer if the type is + * available, or an Uint8Array otherwise. + * + * @param {ArrayBuffer|Buffer} data - gzipped or uncompressed data + * @param {parseCallback} callback + * + * @see module:nbt.parseUncompressed + * @see module:nbt.Reader#compound + * + * @example + * nbt.parse(buf, function(error, results) { + * if (error) { + * throw error; + * } + * console.log(result.name); + * console.log(result.value.foo); + * }); */ + static parse(data: any, callback: any) { + if (!hasGzipHeader(data)) { + callback(null, NBTReader.parseUncompressed(data)) + } else { + callback( + new Error('NBT compressed archive support is not implemented '), + null, + ) + } + } +} diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 4b39d94..43418b5 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -86,16 +86,14 @@ export class ChunkFactory { blockLocalPos.x += 1 // block.localPos.y = patch.bbox.max.y blockLocalPos.z += 1 - const blockType = highlightPatchBorders(blockLocalPos, block.data.type) || block.data.type + const blockType = + highlightPatchBorders(blockLocalPos, block.data.type) || block.data.type const blockMode = block.data.mode // generate ground buffer - let buffSize = MathUtils.clamp(block.data.level - ymin, 0, ymax - ymin) + const buffSize = MathUtils.clamp(block.data.level - ymin, 0, ymax - ymin) if (buffSize > 0) { const groundBuffer = new Uint16Array(buffSize) - const bufferData = this.chunkDataEncoder( - blockType, - blockMode, - ) + const bufferData = this.chunkDataEncoder(blockType, blockMode) groundBuffer.fill(bufferData) // worldChunk.writeSector() const chunkBuffer = worldChunk.readBuffer(asVect2(blockLocalPos)) diff --git a/src/tools/ProceduralGenerators.ts b/src/tools/ProceduralGenerators.ts index b8c0954..4ebcaa3 100644 --- a/src/tools/ProceduralGenerators.ts +++ b/src/tools/ProceduralGenerators.ts @@ -1,4 +1,5 @@ import { Vector3, Vector2, Box3 } from 'three' + import { asVect2 } from '../common/utils' import { ChunkContainer } from '../datacontainers/ChunkContainer' import { BlockType } from '../index' @@ -6,16 +7,16 @@ import { BlockType } from '../index' export enum ProcItemCategory { Tree, Boulder, - Grass + Grass, } export enum ProcItemType { AppleTree, - PineTree + PineTree, } export type ProcItemConf = { - category: ProcItemCategory, + category: ProcItemCategory params: any } @@ -37,25 +38,32 @@ type ProceduralGenerator = TreeGenerator const ProceduralGenerators: Record = { [ProcItemType.AppleTree]: AppleTreeGen, - [ProcItemType.PineTree]: PineTreeGen + [ProcItemType.PineTree]: PineTreeGen, } export class ProceduralItemGenerator { static chunkDataEncoder = (blockType: BlockType) => blockType static voxelizeItem(itemCat: ProcItemCategory, itemParams: any) { + const { treeType, treeSize, treeRadius } = itemParams switch (itemCat) { case ProcItemCategory.Tree: - const { treeType, treeSize, treeRadius } = itemParams return this.voxelizeTree(treeType, treeSize, treeRadius) } - return + return null } - static voxelizeTree(treeType: ProcItemType, treeSize: number, treeRadius: number) { + static voxelizeTree( + treeType: ProcItemType, + treeSize: number, + treeRadius: number, + ) { const { chunkDataEncoder } = ProceduralItemGenerator const treeGenerator = ProceduralGenerators[treeType] - const treeBounds = new Box3(new Vector3(), new Vector3(2 * treeRadius, treeSize + 2 * treeRadius, 2 * treeRadius)) + const treeBounds = new Box3( + new Vector3(), + new Vector3(2 * treeRadius, treeSize + 2 * treeRadius, 2 * treeRadius), + ) const treeChunk = new ChunkContainer(treeBounds) const entityPos = treeBounds.getCenter(new Vector3()) let index = 0 @@ -83,5 +91,4 @@ export class ProceduralItemGenerator { } return treeChunk } - -} \ No newline at end of file +} diff --git a/src/tools/SchematicLoader.ts b/src/tools/SchematicLoader.ts index 1088326..a0322f5 100644 --- a/src/tools/SchematicLoader.ts +++ b/src/tools/SchematicLoader.ts @@ -1,127 +1,139 @@ -import { NBTReader } from "../third-party/nbt_custom"; -import Pako from "pako" -import { Box3, Vector3 } from "three"; -import { BlockType } from "../procgen/Biome"; -import { ChunkContainer } from "../datacontainers/ChunkContainer"; -import { WorldConf } from "../misc/WorldConfig"; +import Pako from 'pako' +import { Box3, Vector3 } from 'three' + +import { NBTReader } from '../third-party/nbt_custom' +import { BlockType } from '../procgen/Biome' +import { ChunkContainer } from '../datacontainers/ChunkContainer' +import { WorldConf } from '../misc/WorldConfig' export class SchematicLoader { - static worldBlocksMapping: Record - static chunkDataEncoder = (blockType: BlockType) => blockType + static worldBlocksMapping: Record + static chunkDataEncoder = (blockType: BlockType) => blockType - static async load(path: string) { - // const schem = await Schematic.read(Buffer.from(schemData), '1.16.4') - const res = await fetch(path); - const blob = await res.blob(); - const rawData = await new Promise((resolve) => { - const reader = new FileReader(); - reader.onload = function (event) { - const blobData = event?.target?.result as ArrayBuffer - blobData && resolve(Pako.inflate(blobData)) - } - reader.readAsArrayBuffer(blob); - }) - return rawData - } + static async load(path: string) { + // const schem = await Schematic.read(Buffer.from(schemData), '1.16.4') + const res = await fetch(path) + const blob = await res.blob() + const rawData = await new Promise(resolve => { + // eslint-disable-next-line no-undef + const reader = new FileReader() + reader.onload = function (event) { + const blobData = event?.target?.result as ArrayBuffer + blobData && resolve(Pako.inflate(blobData)) + } + reader.readAsArrayBuffer(blob) + }) + return rawData + } - static async parse(rawData: any) { - return new Promise((resolve) => { - NBTReader.parse(rawData, function (error: any, data: unknown) { - if (error) { throw error; } - resolve(data); - }); - }); - } + static async parse(rawData: any) { + return new Promise(resolve => { + NBTReader.parse(rawData, function (error: any, data: unknown) { + if (error) { + throw error + } + resolve(data) + }) + }) + } - /** - * convert schematic format to world object - * @param schemBlocks - * @returns - */ - static async createChunkContainer(fileUrl: string) { - const { chunkDataEncoder } = SchematicLoader + /** + * convert schematic format to world object + * @param schemBlocks + * @returns + */ + static async createChunkContainer(fileUrl: string) { + const { chunkDataEncoder } = SchematicLoader - const rawData = await SchematicLoader.load(fileUrl) - const parsedSchematic = await SchematicLoader.parse(rawData) - const schemBlocks: any = SchematicLoader.getBlocks(parsedSchematic) - const dims = new Vector3(schemBlocks[0].length, schemBlocks.length, schemBlocks[0][0].length) - const orig = new Vector3(0, 0, 0) - const end = orig.clone().add(dims) - const bbox = new Box3(orig, end) - const chunkContainer = new ChunkContainer(bbox) + const rawData = await SchematicLoader.load(fileUrl) + const parsedSchematic = await SchematicLoader.parse(rawData) + const schemBlocks: any = SchematicLoader.getBlocks(parsedSchematic) + const dims = new Vector3( + schemBlocks[0].length, + schemBlocks.length, + schemBlocks[0][0].length, + ) + const orig = new Vector3(0, 0, 0) + const end = orig.clone().add(dims) + const bbox = new Box3(orig, end) + const chunkContainer = new ChunkContainer(bbox) - for (let y = 0; y < schemBlocks.length; y++) { - for (let x = 0; x < schemBlocks[y].length; x++) { - for (let z = 0; z < schemBlocks[y][x].length; z++) { - const rawType = schemBlocks[y][x][z].name.split(":")[1] - let blockType = this.worldBlocksMapping[rawType] - if (blockType === undefined) { - console.warn(`missing schematic block type ${rawType}`) - blockType = WorldConf.debug.schematics.missingBlockType - } - // worldObj.rawData[index++] = blockType - const localPos = new Vector3(x, y, z) - const blockIndex = chunkContainer.getIndex(localPos) - // const encodedData = ChunkFactory.defaultInstance.voxelDataEncoder(blockType || BlockType.NONE) - chunkContainer.rawData[blockIndex] = chunkDataEncoder(blockType || BlockType.NONE) //encodedData - } - } + for (let y = 0; y < schemBlocks.length; y++) { + for (let x = 0; x < schemBlocks[y].length; x++) { + for (let z = 0; z < schemBlocks[y][x].length; z++) { + const [, rawType] = schemBlocks[y][x][z].name.split(':') + let blockType = this.worldBlocksMapping[rawType] + if (blockType === undefined) { + console.warn(`missing schematic block type ${rawType}`) + blockType = WorldConf.debug.schematics.missingBlockType + } + // worldObj.rawData[index++] = blockType + const localPos = new Vector3(x, y, z) + const blockIndex = chunkContainer.getIndex(localPos) + // const encodedData = ChunkFactory.defaultInstance.voxelDataEncoder(blockType || BlockType.NONE) + chunkContainer.rawData[blockIndex] = chunkDataEncoder( + blockType || BlockType.NONE, + ) // encodedData } - return chunkContainer + } } + return chunkContainer + } - static getBlocks(schemData: any) { - // Get dimensions of the schematic - const width = schemData.value.Width.value; - const height = schemData.value.Height.value; - const length = schemData.value.Length.value; + static getBlocks(schemData: any) { + // Get dimensions of the schematic + const width = schemData.value.Width.value + const height = schemData.value.Height.value + const length = schemData.value.Length.value - // Get the palette and block data - const palette = schemData.value.Palette.value; - const blockData = schemData.value.BlockData.value; + // Get the palette and block data + const palette = schemData.value.Palette.value + const blockData = schemData.value.BlockData.value - // Create a new 3d array - let skippedBlocks = []; - let blocks: any = []; - for (let y = 0; y < height; y++) { - blocks[y] = []; - for (let x = 0; x < width; x++) { - blocks[y][x] = []; - for (let z = 0; z < length; z++) { - const blockId = blockData[x + z * width + y * width * length]; - const data = this.getBlockData(palette, blockId); - if (data === undefined) { - skippedBlocks.push(blockId); - continue; - } - blocks[y][x][z] = data; - } - } + // Create a new 3d array + const skippedBlocks = [] + const blocks: any = [] + for (let y = 0; y < height; y++) { + blocks[y] = [] + for (let x = 0; x < width; x++) { + blocks[y][x] = [] + for (let z = 0; z < length; z++) { + const blockId = blockData[x + z * width + y * width * length] + const data = this.getBlockData(palette, blockId) + if (data === undefined) { + skippedBlocks.push(blockId) + continue + } + blocks[y][x][z] = data } - if (skippedBlocks.length > 0) { - console.warn("Failed to get block data for: " + skippedBlocks); - } - return blocks; + } + } + if (skippedBlocks.length > 0) { + console.warn('Failed to get block data for: ' + skippedBlocks) } + return blocks + } - static getBlockData(palette: any, blockId: number) { - // Iterate through each key pair in the palette values - for (const [key, value] of Object.entries(palette)) { - if ((value as any).value === blockId) { - // If the key contains a closing bracket, return only everything before the bracket - if (key.includes("[")) { - return { - name: key.substring(0, key.indexOf("[")), - properties: key.substring(key.indexOf("[") + 1, key.indexOf("]")).split(",") - }; - } - return { - name: key, - }; - } + static getBlockData(palette: any, blockId: number) { + // Iterate through each key pair in the palette values + for (const [key, value] of Object.entries(palette)) { + if ((value as any).value === blockId) { + // If the key contains a closing bracket, return only everything before the bracket + if (key.includes('[')) { + return { + name: key.substring(0, key.indexOf('[')), + properties: key + .substring(key.indexOf('[') + 1, key.indexOf(']')) + .split(','), + } } return { - name: "minecraft:air", - }; + name: key, + } + } + } + return { + name: 'minecraft:air', } -} \ No newline at end of file + } +} diff --git a/tsconfig.json b/tsconfig.json index 28bf720..600fac1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,7 @@ /* Modules */ "module": "ES6", - "moduleResolution": "Bundler", + "moduleResolution": "bundler", "typeRoots": ["src/@types", "./node_modules/@types"], /* Emit */