From cfe2b2c66c5c31ad884227b9a1148a5b0a8ee620 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 18 Sep 2024 08:38:38 +0000 Subject: [PATCH 01/13] wip: settings format refactor --- src/api/world-compute.ts | 41 ++++++++--- src/common/misc.ts | 36 ++++++++-- src/common/types.ts | 49 ++++++++----- src/common/utils.ts | 38 +++++----- src/datacontainers/GroundPatch.ts | 5 ++ src/datacontainers/WorldChunk.ts | 3 +- src/procgen/Biome.ts | 113 ++++++++++++++++-------------- src/procgen/Heightmap.ts | 12 ++-- src/procgen/ProcLayer.ts | 3 - 9 files changed, 185 insertions(+), 115 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 82259a3..71289d3 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,13 +1,13 @@ -import { Box2, Vector3 } from 'three' +import { Box2, Vector2, Vector3 } from 'three' import { EntityType, GroundPatch } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { Block, EntityData, PatchKey } from '../common/types' -import { asBox3, asVect2, asVect3 } from '../common/utils' +import { Block, EntityData, NoiseLevelConf, PatchKey } from '../common/types' +import { asBox3, asVect2, asVect3, getBoundsCornerPoints } from '../common/utils' import { WorldEntities } from '../procgen/WorldEntities' import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' -import { BlockData } from '../datacontainers/GroundPatch' +import { BlockData, GroundRawData } from '../datacontainers/GroundPatch' // import { BoardInputParams } from '../feats/BoardContainer' /** @@ -57,16 +57,33 @@ export const computeBlocksBatch = ( export const computeGroundBlock = (blockPos: Vector3) => { const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) - const mainBiome = Biome.instance.getMainBiome(biomeContribs) + const biomeType = Biome.instance.getBiomeType(biomeContribs) const rawVal = Heightmap.instance.getRawVal(blockPos) - const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) + const noiseLevel = Biome.instance.getBiomeConf(rawVal, biomeType) as NoiseLevelConf + const currLevelConf = noiseLevel.data + const prevLevelConf = noiseLevel.prev?.data + const nextLevelConf = noiseLevel.next?.data + const confIndex = Biome.instance.getConfIndex(currLevelConf.key) + // const confData = Biome.instance.indexedConf.get(confIndex) const level = Heightmap.instance.getGroundLevel( blockPos, rawVal, biomeContribs, ) // const pos = new Vector3(blockPos.x, level, blockPos.z) - const type = blockTypes.grounds[0] as BlockType + 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) + 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 + const type = variation > threshold && prevType ? prevType : currLevelConf.type + if (!type) { + console.log(currLevelConf) + } // const entityType = blockTypes.entities?.[0] as EntityType // let offset = 0 // if (lastBlock && entityType) { @@ -74,6 +91,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { // } // level += offset const block: BlockData = { level, type } + // const block: GroundRawData = { rawVal, confIndex } return block } @@ -84,6 +102,9 @@ export const computeGroundBlock = (blockPos: Vector3) => { // Ground export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { const groundPatch = new GroundPatch(boundsOrPatchKey) + // eval biome at patch corners + const patchCorners = getBoundsCornerPoints(groundPatch.bounds) + // patchCorners.map(point=>Biome.instance.getBiomeInfluence()) const { min, max } = groundPatch.bounds const blocks = groundPatch.iterBlocksQuery(undefined, false) const level = { @@ -138,9 +159,9 @@ const confirmFinalizeEntity = (entity: EntityData) => { const entityPos = entity.bbox.getCenter(new Vector3()) // use global coords in case entity center is from adjacent patch const rawVal = Heightmap.instance.getRawVal(entityPos) - const mainBiome = Biome.instance.getMainBiome(entityPos) - const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) - const entityType = blockTypes.entities?.[0] as EntityType + const biomeType = Biome.instance.getBiomeType(entityPos) + const biomeConf = Biome.instance.getBiomeConf(rawVal, biomeType) + const [entityType] = biomeConf.data.entities || [] // confirm this kind of entity can spawn over here if (entityType) { entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos, rawVal) diff --git a/src/common/misc.ts b/src/common/misc.ts index ccc4941..538c3b6 100644 --- a/src/common/misc.ts +++ b/src/common/misc.ts @@ -11,19 +11,41 @@ export class LinkedList { // finder?: (item: T, val: any) => boolean first() { - let curr: LinkedList = this - while (curr.prev) { - curr = curr.prev + const items = this.backwardIter() + let first = this as LinkedList + for (const item of items) { + first = item } - return curr + return first } last() { - let curr: LinkedList = this - while (curr.next) { + const items = this.forwardIter() + let last = this as LinkedList + for (const item of items) { + last = item + } + return last + } + + *backwardIter() { + let curr: LinkedList | undefined = this + // yield curr + // do{ + // yield curr.prev + // }while(curr.prev) + while (curr) { + yield curr + curr = curr.prev + } + } + + *forwardIter() { + let curr: LinkedList | undefined = this + while (curr) { + yield curr curr = curr.next } - return curr } // find(val: any) { diff --git a/src/common/types.ts b/src/common/types.ts index d21a182..b1e2fa7 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -16,6 +16,24 @@ export type PatchBlock = Block & { localPos: Vector3 } +export enum CardinalDirections { + N, + E, + S, + W, +} + +export enum IntercardinalDirections { + NE, + NW, + SE, + SW, +} + +export type AllCardinalDirections = + | CardinalDirections + | IntercardinalDirections + export enum Adjacent2dPos { center, left, @@ -89,25 +107,22 @@ export type ProcLayerExtCfg = { // MOUNTAINS_TOP, // } -export interface MappingData { - grounds: BlockType[] // which types of ground can be here - entities: string[] // which type of entities can spawn - amplitude: { - // amplitude used in blocks randomization - low: number - high: number - } +export type MetadataFields = { + key: BiomeConfKey, + 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 + entities: string[] // which entities can spawn + fadein: any, + fadeout: any } -export interface MappingRange extends Partial { - x: number // noise - y: number // noise mapping -} -export type MappingConf = Record -export type MappingRanges = LinkedList -export type BiomeConf = Record -export type BiomeMappings = Record +export type NoiseLevelMappings = Record> +export type BiomeConfigs = Record +export type NoiseLevelConf = LinkedList export enum EntityType { NONE = '', @@ -124,6 +139,8 @@ export type EntityData = { } } +export type NoiseLevelId = string // an arbitrary name given to help identify noise level +export type BiomeConfKey = string // combination of noiseLevelId and biomeType export type EntityKey = string export type PatchKey = string diff --git a/src/common/utils.ts b/src/common/utils.ts index fb2da56..9346eb5 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -5,8 +5,8 @@ import { Adjacent3dPos, ChunkId, ChunkKey, - MappingRange, - MappingRanges, + MetadataFields, + NoiseLevelConf, PatchId, PatchKey, } from './types' @@ -32,7 +32,7 @@ const vectRoundToDec = (input: Vector2 | Vector3, n_pow: number) => { } // const MappingRangeFinder = (item: LinkedList, inputVal: number) => item.next && inputVal > (item.next.data as MappingData).x -export const MappingRangeSorter = (item1: MappingRange, item2: MappingRange) => +export const MappingRangeSorter = (item1: MetadataFields, item2: MetadataFields) => item1.x - item2.x /** @@ -40,8 +40,8 @@ export const MappingRangeSorter = (item1: MappingRange, item2: MappingRange) => * @param inputVal * @returns */ -const findMatchingRange = (inputVal: number, mappingRanges: MappingRanges) => { - let match = mappingRanges.first() +const findMatchingRange = (inputVal: number, noiseMappings: NoiseLevelConf) => { + let match = noiseMappings.first() while (match.next && inputVal > match.next.data.x) { match = match.next } @@ -194,17 +194,13 @@ const getNeighbours3D = ( return neighbours.map(type => getAdjacent3dCoords(pos, type as number)) } -const getPatchPoints = (patchBBox: Box3, clearY = true) => { - const { min, max } = patchBBox.clone() - if (clearY) { - min.y = 0 - max.y = 0 - } - const minXmaxZ = min.clone() - minXmaxZ.z = max.z - const maxXminZ = min.clone() - maxXminZ.x = max.x - const points = [min, max, minXmaxZ, maxXminZ] +const getBoundsCornerPoints = (bounds: Box2) => { + const { min, max } = bounds + const xMyP = min.clone() + xMyP.y = max.y + const xPyM = min.clone() + xPyM.x = max.x + const points = [min, max, xMyP, xPyM] return points } @@ -290,10 +286,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 } @@ -404,7 +400,7 @@ export { getNeighbours2D, getNeighbours3D, bboxContainsPointXZ, - getPatchPoints, + getBoundsCornerPoints, parseThreeStub, asVect2, asVect3, diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index eda37b5..f34a62d 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -17,6 +17,11 @@ export enum BlockMode { BOARD_CONTAINER, } +export type GroundRawData = { + rawVal: number + confIndex: number +} + export type BlockData = { level: number type: BlockType diff --git a/src/datacontainers/WorldChunk.ts b/src/datacontainers/WorldChunk.ts index 31acd87..1baee8c 100644 --- a/src/datacontainers/WorldChunk.ts +++ b/src/datacontainers/WorldChunk.ts @@ -51,7 +51,8 @@ export class WorldChunk { blockLocalPos.z * Math.pow(chunk_size, 2) + h * chunk_size + blockLocalPos.x - const blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type + const groundType = blockData.type//depth > 0 && bufferOver.length === 0 ? BlockType.ROCK : blockData.type + const blockType = buff_index > 0 ? bufferOver[buff_index] : groundType const skip = buff_index > 0 && chunkData[blocksIndex] !== undefined && diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 98f23ca..1ff40d5 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -1,17 +1,12 @@ import { Vector2, Vector3 } from 'three' // import { MappingProfiles, ProfilePreset } from "../tools/MappingPresets" -import { - BiomeConf, - BiomeMappings, - MappingData, - MappingRanges, -} from '../common/types' import { LinkedList } from '../common/misc' import { MappingRangeSorter } from '../common/utils' import * as Utils from '../common/utils' import { ProcLayer } from './ProcLayer' +import { BiomeConfigs, BiomeConfKey, NoiseLevelConf } from '../common/types' export enum BlockType { NONE, @@ -86,9 +81,9 @@ export class Biome { // heatProfile: MappingRanges // rainProfile: MappingRanges - mappings = {} as BiomeMappings + mappings = {} as BiomeConfigs posRandomizer: ProcLayer - transitionLevels = { + triggerLevels = { low: 0.3, mid_low: 0.4, mid: 0.5, @@ -100,7 +95,9 @@ export class Biome { seaLevel: 0, } - constructor(biomeConf?: BiomeConf) { + indexedConf = new Map + + constructor(biomeConf?: BiomeConfigs) { this.heatmap = new ProcLayer('heatmap') this.heatmap.sampling.harmonicsCount = 6 this.heatmap.sampling.periodicity = 8 @@ -112,7 +109,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.setMappings(biomeConf) + if (biomeConf) this.parseBiomesConfig(biomeConf) } static get instance() { @@ -120,12 +117,18 @@ export class Biome { return Biome.singleton } + getConfIndex(confKey: BiomeConfKey) { + const confKeys = [...this.indexedConf.keys()]; // Spread keys into an array + const confIndex = confKeys.indexOf(confKey); // Find the index of 'key2' + return confIndex + } + /** * * @param input either blocks position, or pre-requested biome contributions * @returns */ - getMainBiome(input: Vector3 | BiomeInfluence): BiomeType { + getBiomeType(input: Vector3 | BiomeInfluence): BiomeType { const biomeContribs = input instanceof Vector3 ? this.getBiomeInfluence(input) : input const mainBiome = Object.entries(biomeContribs).sort( @@ -134,7 +137,7 @@ export class Biome { return mainBiome as BiomeType } - getBiomeInfluence(pos: Vector3): BiomeInfluence { + getBiomeInfluence(pos: Vector2 | Vector3): BiomeInfluence { const heatContribs: HeatContribs = { [Heat.Cold]: 0, [Heat.Temperate]: 0, @@ -150,31 +153,31 @@ export class Biome { [BiomeType.Artic]: 0, [BiomeType.Desert]: 0, } - const { transitionLevels } = this + const { low, mid_low, mid_high, high } = this.triggerLevels const heatVal = this.heatmap.eval(pos) // Utils.roundToDec(this.heatmap.eval(pos), 2) const rainVal = this.rainmap.eval(pos) // Utils.roundToDec(this.rainmap.eval(pos), 2) // TEMPERATURE // cold - if (heatVal <= transitionLevels.low) { + if (heatVal <= low) { heatContribs.cold = 1 } // cold to temperate transition - else if (heatVal <= transitionLevels.mid_low) { + else if (heatVal <= mid_low) { heatContribs.temperate = - (heatVal - transitionLevels.low) / - (transitionLevels.mid_low - transitionLevels.low) + (heatVal - low) / + (mid_low - low) heatContribs.cold = 1 - heatContribs.temperate } // temperate - else if (heatVal <= transitionLevels.mid_high) { + else if (heatVal <= mid_high) { heatContribs.temperate = 1 } // temperate to hot transition - else if (heatVal <= transitionLevels.high) { + else if (heatVal <= high) { heatContribs.hot = - (heatVal - transitionLevels.mid_high) / - (transitionLevels.high - transitionLevels.mid_high) + (heatVal - mid_high) / + (high - mid_high) heatContribs.temperate = 1 - heatContribs.hot } // hot @@ -184,25 +187,25 @@ export class Biome { // HUMIDITY // dry - if (rainVal <= transitionLevels.low) { + if (rainVal <= low) { rainContribs.dry = 1 } // dry => moderate transition - else if (rainVal <= transitionLevels.mid_low) { + else if (rainVal <= mid_low) { rainContribs.moderate = - (rainVal - transitionLevels.low) / - (transitionLevels.mid_low - transitionLevels.low) + (rainVal - low) / + (mid_low - low) rainContribs.dry = 1 - rainContribs.moderate } // moderate - else if (rainVal <= transitionLevels.mid_high) { + else if (rainVal <= mid_high) { rainContribs.moderate = 1 } // moderate to wet transition - else if (rainVal <= transitionLevels.high) { + else if (rainVal <= high) { rainContribs.wet = - (rainVal - transitionLevels.mid_high) / - (transitionLevels.high - transitionLevels.mid_high) + (rainVal - mid_high) / + (high - mid_high) rainContribs.moderate = 1 - rainContribs.wet } // wet @@ -218,10 +221,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 @@ -230,21 +233,28 @@ export class Biome { return biomeContribs } - setMappings(biomeConf: BiomeConf) { - Object.entries(biomeConf).forEach(([biomeType, mappingConf]) => { - const mappingItems = Object.values(mappingConf) + parseBiomesConfig(biomeConfigs: BiomeConfigs) { + Object.entries(biomeConfigs).forEach(([biomeType, biomeConf]) => { + // complete missing data + Object.entries(biomeConf).forEach(([confId, confData]) => confData.key = biomeType + '_' + confId) + const configItems = Object.values(biomeConf) const mappingRanges = LinkedList.fromArrayAfterSorting( - mappingItems, + configItems, MappingRangeSorter, ) this.mappings[biomeType as BiomeType] = mappingRanges + // index configs + const confIter = mappingRanges.first().forwardIter() + for (const conf of confIter) { + this.indexedConf.set(conf.data.key, conf) + } }) } - stepTransition = ( + noiseLevelTransition = ( groundPos: Vector2, baseHeight: number, - blockMapping: MappingRanges, + blockMapping: NoiseLevelConf, ) => { const period = 0.005 * Math.pow(2, 2) const mapCoords = groundPos.clone().multiplyScalar(period) @@ -255,7 +265,7 @@ export class Biome { lower: blockMapping.data.x, upper: blockMapping.next?.data.x || 1, } - let blockTypes + let blockType // randomize on lower side if ( blockMapping.prev && @@ -264,10 +274,10 @@ export class Biome { ) { const heightVariation = posRandomizerVal * amplitude.low const varyingHeight = baseHeight - heightVariation - blockTypes = + blockType = varyingHeight < blockMapping.data.x - ? blockMapping.prev?.data.grounds - : blockMapping.data.grounds + ? blockMapping.prev?.data.type + : blockMapping.data.type } // randomize on upper side else if (blockMapping.next && baseHeight + amplitude.high > bounds.upper) { @@ -276,12 +286,12 @@ export class Biome { // heightVariation = heightVariation > 0 ? (heightVariation - 0.5) * 2 : 0 const heightVariation = posRandomizerVal * amplitude.high const varyingHeight = baseHeight + heightVariation - blockTypes = + blockType = varyingHeight > blockMapping.next.data.x - ? blockMapping.next.data.grounds - : blockMapping.data.grounds + ? blockMapping.next.data.type + : blockMapping.data.type } - return blockTypes?.[0] + return blockType } getBlockLevel = ( @@ -316,18 +326,19 @@ export class Biome { return blockLevel } - getBlockType = (rawVal: number, biomeType: BiomeType) => { + getBiomeConf = (rawVal: number, biomeType: BiomeType) => { // nominal block type let mappingRange = Utils.findMatchingRange( rawVal as number, - this.mappings[biomeType as BiomeType], + this.mappings[biomeType], ) - while (!mappingRange.data.grounds && mappingRange.prev) { + while (!mappingRange.data.type && mappingRange.prev) { mappingRange = mappingRange.prev } + const biomeConfKey = mappingRange.data.key // const finalBlockType = this.blockRandomization(groundPos, baseHeight, currentBlockMap) // if (finalBlockType !== nominalBlockType) console.log(`[getBlockType] nominal${nominalBlockType} random${finalBlock}`) - return mappingRange.data as MappingData + return this.indexedConf.get(biomeConfKey) } } diff --git a/src/procgen/Heightmap.ts b/src/procgen/Heightmap.ts index 0cadb7d..dafd422 100644 --- a/src/procgen/Heightmap.ts +++ b/src/procgen/Heightmap.ts @@ -71,14 +71,14 @@ export class Heightmap { // includeSea?: boolean, ) { rawVal = rawVal || this.getRawVal(blockPos) - 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( - rawVal, - biomeInfluence, - ) - // Biome.instance.getBlockLevel(rawVal, BiomeType.Artic) + // const initialVal = Biome.instance.getBlockLevelInterpolated( + // rawVal, + // biomeInfluence, + // ) + const initialVal = Biome.instance.getBlockLevel(rawVal, Biome.instance.getBiomeType(biomeInfluence)) const finalVal = this.applyModulation( blockPos, initialVal, diff --git a/src/procgen/ProcLayer.ts b/src/procgen/ProcLayer.ts index 7a6545f..e4b2c4b 100644 --- a/src/procgen/ProcLayer.ts +++ b/src/procgen/ProcLayer.ts @@ -1,5 +1,3 @@ -import { MappingRanges } from '../common/types' - import { InputType, NoiseSampler } from './NoiseSampler' /** @@ -9,7 +7,6 @@ export class ProcLayer { name: string // layer identifier parent: any sampling!: NoiseSampler - mapping!: MappingRanges params = { spreading: 0, scaling: 0.001, From f979d8b91251ffbc386784012df52497554d0443 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 20 Sep 2024 07:03:23 +0000 Subject: [PATCH 02/13] wip schematics + refactor --- package.json | 2 + src/api/WorldComputeProxy.ts | 42 +-- src/api/world-compute.ts | 75 ++-- src/common/utils.ts | 11 + src/datacontainers/ChunkContainer.ts | 352 ++++++++++++++++++ src/datacontainers/EntityChunk.ts | 99 ----- src/datacontainers/GroundPatch.ts | 25 +- src/datacontainers/OvergroundEntities.ts | 59 +++ .../{DataContainers.ts => PatchContainer.ts} | 2 +- src/datacontainers/WorldChunk.ts | 78 ---- src/feats/BoardContainer.ts | 6 +- src/index.ts | 9 +- src/misc/WorldConfig.ts | 6 +- src/procgen/WorldEntities.ts | 60 --- src/third-party/nbt.ts | 308 +++++++++++++++ src/tools/ChunkFactory.ts | 98 +++-- src/tools/ProceduralGenerators.ts | 91 +++++ src/tools/SchematicLoader.ts | 132 +++++++ src/tools/TreeGenerator.ts | 25 -- 19 files changed, 1122 insertions(+), 358 deletions(-) create mode 100644 src/datacontainers/ChunkContainer.ts delete mode 100644 src/datacontainers/EntityChunk.ts create mode 100644 src/datacontainers/OvergroundEntities.ts rename src/datacontainers/{DataContainers.ts => PatchContainer.ts} (98%) delete mode 100644 src/datacontainers/WorldChunk.ts delete mode 100644 src/procgen/WorldEntities.ts create mode 100644 src/third-party/nbt.ts create mode 100644 src/tools/ProceduralGenerators.ts create mode 100644 src/tools/SchematicLoader.ts delete mode 100644 src/tools/TreeGenerator.ts diff --git a/package.json b/package.json index 72113cc..2625ec7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ }, "keywords": [], "dependencies": { + "@types/pako": "^2.0.3", "alea": "^1.0.1", + "pako": "^2.1.0", "poisson-disk-sampling": "^2.3.1", "simplex-noise": "^4.0.3" }, diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index 12e524d..ca21fea 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -102,18 +102,18 @@ export class WorldComputeProxy { // } async queryEntities(queriedRegion: Box2) { - const entitiesData = !this.worker - ? WorldCompute.queryEntities(queriedRegion) - : ((await this.workerCall( - ComputeApiCall.QueryEntities, - [queriedRegion], // [emptyPatch.bbox] - )?.then(stubs => - stubs.map((stub: EntityData) => ({ - ...stub, - bbox: parseThreeStub(stub.bbox), - })), - )) as EntityData[]) - return entitiesData + // const entitiesData = !this.worker + // ? WorldCompute.queryEntities(queriedRegion) + // : ((await this.workerCall( + // ComputeApiCall.QueryEntities, + // [queriedRegion], // [emptyPatch.bbox] + // )?.then(stubs => + // stubs.map((stub: EntityData) => ({ + // ...stub, + // bbox: parseThreeStub(stub.bbox), + // })), + // )) as EntityData[]) + return [] //entitiesData } async *iterPatchCompute(patchKeysBatch: PatchKey[]) { @@ -141,15 +141,15 @@ export class WorldComputeProxy { } async bakeEntities(queriedRange: Box2) { - const entityChunks = !this.worker - ? WorldCompute.queryBakeEntities(queriedRange) - : await this.workerCall(ComputeApiCall.BakeEntities, [ - queriedRange, - ])?.then((entityChunks: EntityChunkStub[]) => - // parse worker's data to recreate original objects - entityChunks.map(chunkStub => EntityChunk.fromStub(chunkStub)), - ) - return entityChunks + // const entityChunks = !this.worker + // ? WorldCompute.queryBakeEntities(queriedRange) + // : await this.workerCall(ComputeApiCall.BakeEntities, [ + // queriedRange, + // ])?.then((entityChunks: EntityChunkStub[]) => + // // parse worker's data to recreate original objects + // entityChunks.map(chunkStub => EntityChunk.fromStub(chunkStub)), + // ) + return []//entityChunks } // async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) { diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 71289d3..e9b2f02 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -3,11 +3,10 @@ import { Box2, Vector2, Vector3 } from 'three' import { EntityType, GroundPatch } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { Block, EntityData, NoiseLevelConf, PatchKey } from '../common/types' -import { asBox3, asVect2, asVect3, getBoundsCornerPoints } from '../common/utils' -import { WorldEntities } from '../procgen/WorldEntities' -import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' -import { BlockData, GroundRawData } from '../datacontainers/GroundPatch' +import { Block, EntityData, PatchKey } from '../common/types' +import { asBox3, asVect2, asVect3 } from '../common/utils' +import { BlockData } from '../datacontainers/GroundPatch' +import { OvergroundEntities, WorldObjectType } from '../datacontainers/OvergroundEntities' // import { BoardInputParams } from '../feats/BoardContainer' /** @@ -28,23 +27,27 @@ export const computeBlocksBatch = ( const blocksBatch = blockPosBatch.map(({ x, z }) => { const blockPos = new Vector3(x, 0, z) const blockData = computeGroundBlock(blockPos) - if (includeEntitiesBlocks) { - const entityRange = new Box2().setFromPoints([asVect2(blockPos)]) - entityRange.max.addScalar(1) - const [foundEntity] = queryEntities(entityRange).map(entityData => { - const { min, max } = entityData.bbox - const custChunkBox = asBox3(entityRange) - custChunkBox.min.y = min.y - custChunkBox.max.y = max.y - return new EntityChunk(entityData, custChunkBox) - }) - const blocksBuffer = foundEntity?.voxelize() + const { spawnableItems } = blockData + const queriedLoc = new Box2().setFromPoints([asVect2(blockPos)]) + queriedLoc.max.addScalar(1) + false && includeEntitiesBlocks && spawnableItems.forEach(entityType => { + // multiple (overlapping) objects may be found at queried position + const [spawnedEntity] = OvergroundEntities.querySpawnedEntities(entityType, queriedLoc) + + // const [foundEntity] = queryEntities(spawnRange).map(entityData => { + // const { min, max } = entityData.bbox + // const custChunkBox = asBox3(entityRange) + // custChunkBox.min.y = min.y + // custChunkBox.max.y = max.y + // return new EntityChunk(entityData, custChunkBox) + // }) + // foundEntity. const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { blockData.level += lastBlockIndex blockData.type = blocksBuffer[lastBlockIndex] as BlockType } - } + }) blockPos.y = blockData.level const block: Block = { pos: blockPos, @@ -84,14 +87,15 @@ export const computeGroundBlock = (blockPos: Vector3) => { if (!type) { console.log(currLevelConf) } + const type = blockTypes.grounds[0] as BlockType + const spawnableItems = blockTypes.entities // const entityType = blockTypes.entities?.[0] as EntityType // let offset = 0 // if (lastBlock && entityType) { // } // level += offset - const block: BlockData = { level, type } - // const block: GroundRawData = { rawVal, confIndex } + const block: BlockData = { level, type, spawnableItems } return block } @@ -99,7 +103,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { * Patch requests */ -// Ground +// Patch ground layer export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { const groundPatch = new GroundPatch(boundsOrPatchKey) // eval biome at patch corners @@ -122,6 +126,18 @@ export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { return groundPatch } +/** + * patch is an assembly of several layers + * - ground + * - underground caverns + * - overground objects + */ +export const bakePatchLayers = () => { } +export const bakePatchGroundLayer = () => { } +export const bakePatchUndergroundLayer = () => { } // or caverns +export const bakePatchOvergroundLayer = (boundsOrPatchKey: PatchKey | Box2, objectType: WorldObjectType) => { } + + // Battle board // export const computeBoardData = (boardPos: Vector3, boardParams: BoardInputParams, lastBoardBounds: Box2) => { // const boardMap = new BoardContainer(boardPos, boardParams, lastBoardBounds) @@ -135,7 +151,10 @@ export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { * Entity queries/baking */ -export const queryEntities = (queriedRegion: Box2) => { +export const queryEntities = (queriedRegion: Box2, queriedObjType: WorldObjectType) => { + const queriedObject = OvergroundObjects.getObjectInstance(queriedObjType) + + // const spawnablePlaces = queriedObject.queryDistribution(queriedRegion) const spawnablePlaces = WorldEntities.instance.queryDistributionMap( EntityType.TREE_APPLE, )(queriedRegion) @@ -177,13 +196,13 @@ export const queryBakeEntities = (queriedRange: Box2) => { } export const bakeEntitiesBatch = (entities: EntityData[]) => { - const entitiesChunks: EntityChunkStub[] = entities - .map(entityData => new EntityChunk(entityData)) - .map(entityChunk => { - entityChunk.voxelize() - return entityChunk.toStub() - }) - return entitiesChunks + // const entitiesChunks: EntityChunkStub[] = entities + // .map(entityData => new EntityChunk(entityData)) + // .map(entityChunk => { + // entityChunk.voxelize() + // return entityChunk.toStub() + // }) + return [];//entitiesChunks } // /** diff --git a/src/common/utils.ts b/src/common/utils.ts index 9346eb5..ded0f78 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -391,6 +391,16 @@ const chunkBoxFromId = (chunkId: ChunkId, patchSize: number) => { return chunkBbox } +const chunkBoxFromKey = (chunkKey: string, chunkDims: Vector3) => { + const chunkId = parseChunkKey(chunkKey) + const bbox = new Box3() + if (chunkId) { + bbox.min = chunkId.clone().multiply(chunkDims) + bbox.max = chunkId.clone().addScalar(1).multiply(chunkDims) + } + return bbox +} + export { roundToDec, vectRoundToDec, @@ -417,5 +427,6 @@ export { parseChunkKey, serializeChunkId, chunkBoxFromId, + chunkBoxFromKey, genChunkIds, } diff --git a/src/datacontainers/ChunkContainer.ts b/src/datacontainers/ChunkContainer.ts new file mode 100644 index 0000000..c9330b5 --- /dev/null +++ b/src/datacontainers/ChunkContainer.ts @@ -0,0 +1,352 @@ +import { Vector2, Box2, Box3, Vector3, MathUtils } from 'three' +import { ChunkId, ChunkKey } from '../common/types' +import { asVect3, chunkBoxFromKey, parseChunkKey, serializeChunkId } from '../common/utils' +import { WorldConf } from '../misc/WorldConfig' +import { ChunkFactory } from '../tools/ChunkFactory' +import { BlockData, BlockMode } from './GroundPatch' + +enum ChunkAxisOrder { + 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) + } + + get id() { + return this.chunkId + } + + set id(chunkId: Vector3 | undefined) { + this.chunkId = chunkId + this.chunkKey = serializeChunkId(chunkId) + } + + get extendedBounds() { + return this.bounds.clone().expandByScalar(this.margin) + } + + get extendedDims() { + return this.extendedBounds.getSize(new Vector3()) + } + + get localBox() { + const localBox = new Box3(new Vector3(0), this.dimensions.clone()) + return localBox + } + + 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 global pos region of both containers + static copySourceOverTargetContainer(source: any, target: any) { + const adjustOverlapMargins = (overlap: Box2) => { + const margin = Math.min(target.margin, source.margin) || 0 + overlap.min.x -= target.bounds.min.x === overlap.min.x ? margin : 0 + overlap.min.y -= target.bounds.min.y === overlap.min.y ? margin : 0 + overlap.max.x += target.bounds.max.x === overlap.max.x ? margin : 0 + overlap.max.y += target.bounds.max.y === overlap.max.y ? margin : 0 + } + + if (source.bounds.intersectsBox(target.bounds)) { + const overlap = target.bounds.clone().intersect(source.bounds) + adjustOverlapMargins(overlap) + for (let { y } = overlap.min; y < overlap.max.y; y++) { + // const globalStartPos = new Vector3(x, 0, overlap.min.y) + const globalStartPos = new Vector2(overlap.min.x, y) + const targetLocalStartPos = target.toLocalPos(globalStartPos) + const sourceLocalStartPos = source.toLocalPos(globalStartPos) + let targetIndex = target.getIndex(targetLocalStartPos) + let sourceIndex = source.getIndex(sourceLocalStartPos) + for (let { x } = overlap.min; x < overlap.max.x; x++) { + const sourceVal = source.rawData[sourceIndex] + if (sourceVal) { + target.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) + } + + 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) + } + + 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 + ) + } + + 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 + ) + } + + 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++ + } + } + } + } + + encodeSectorData(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) + } + + writeSector(pos: Vector3, sectorData: number) { + const sectorIndex = this.getIndex(this.toLocalPos(pos)) + this.rawData[sectorIndex] = this.encodeSectorData(sectorData) + } + + readBufferY(localPos: Vector2) { + // const localPos = this.toLocalPos(asVect3(pos, this.bounds.min.y)) + const buffIndex = this.getIndex(localPos) + // const buffIndex = + // chunkLocalPos.z * chunkDims.x * chunkDims.y + + // chunkLocalPos.x * chunkDims.y + const rawBuffer = this.rawData.slice(buffIndex, buffIndex + this.dimensions.y) + return rawBuffer + } + + writeBufferY(pos: Vector2, blocksBuffer: number[]) { + const localPos = this.toLocalPos(asVect3(pos, this.bounds.min.y)) + const {extendedDims}=this + //const startOffset = this.getIndex(localPos) + //this.rawData.set(buffer, startOffset) + while(blocksBuffer.length>0){ + const index = localPos.z * extendedDims.z + + blocksBuffer.length * extendedDims.y + + localPos.x + const blockData = blocksBuffer.pop() + this.rawData[index] = ChunkFactory.defaultInstance.voxelDataEncoder( + blockData.type, + blockData.mode, + ) + } + } + + /** + * From native world format (z,x,y) to (z,y,x) format + */ + exportAsZYXFormat() { + const dimensions = this.bounds.getSize(new Vector3()) + const getTargetIndex = (localPos: Vector3) => localPos.z * dimensions.x * dimensions.y + + localPos.y * dimensions.x + + localPos.x + // read yBuffer at z,x pos + } + + writeBuffer( + blockLocalPos: Vector3, + blockData: BlockData, + bufferOver: Uint16Array | [], + ) { + const { bounds, rawData } = this + const chunk_size = bounds.getSize(new Vector3()).x // Math.round(Math.pow(chunkData.length, 1 / 3)) + + let written_blocks_count = 0 + + const level = MathUtils.clamp( + blockLocalPos.y + bufferOver.length, + bounds.min.y, + bounds.max.y, + ) + let buff_index = Math.max(level - blockLocalPos.y, 0) + let h = level - bounds.min.y // local height + // debug_mode && is_edge(local_pos.z, local_pos.x, h, patch_size - 2) + // ? BlockType.SAND + // : block_cache.type + let depth = 0 + while (h >= 0) { + const blocksIndex = + blockLocalPos.z * Math.pow(chunk_size, 2) + + h * chunk_size + + blockLocalPos.x + const blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type + const skip = + buff_index > 0 && + rawData[blocksIndex] !== undefined && + !bufferOver[buff_index] + if (!skip && blockType !== undefined) { + // #hack: disable block mode below ground to remove checkerboard excess + const skipBlockMode = + depth > 0 && + (bufferOver.length === 0 || bufferOver[buff_index] || buff_index < 0) + const blockMode = skipBlockMode ? BlockMode.DEFAULT : blockData.mode + rawData[blocksIndex] = ChunkFactory.defaultInstance.voxelDataEncoder( + blockType, + blockMode, + ) + blockType && written_blocks_count++ + } + h-- + buff_index-- + depth++ + } + return written_blocks_count + } + + // abstract get chunkIds(): ChunkId[] + // abstract toChunks(): any +} diff --git a/src/datacontainers/EntityChunk.ts b/src/datacontainers/EntityChunk.ts deleted file mode 100644 index 4dc0948..0000000 --- a/src/datacontainers/EntityChunk.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Box3, Vector3, Vector2 } from 'three' - -import { EntityData } from '../common/types' -import { asVect2 } from '../common/utils' -import { BlockType, WorldUtils } from '../index' -import { TreeGenerators } from '../tools/TreeGenerator' - -import { WorldChunk } from './WorldChunk' - -export type EntityChunkStub = { - box: Box3 - data: Uint16Array - entity?: EntityData -} - -const adjustChunkBox = (entityBox: Box3, chunkBox?: Box3) => { - if (chunkBox instanceof Vector3) { - const blockStart = new Vector3(chunkBox.x, entityBox.min.y, chunkBox.z) - const blockEnd = blockStart - .clone() - .add(new Vector3(1, entityBox.max.y - entityBox.min.y, 1)) - chunkBox = new Box3(blockStart, blockEnd) - } - - return chunkBox || entityBox -} - -export class EntityChunk extends WorldChunk { - entityData: EntityData - - constructor(entityData: EntityData, customChunkBox?: Box3) { - super(adjustChunkBox(entityData.bbox, customChunkBox)) - this.entityData = entityData - } - - voxelize() { - const { bbox, params, type } = this.entityData - const { size: treeSize, radius: treeRadius } = params - const entityPos = bbox.getCenter(new Vector3()) - const { min, max } = this.chunkBox - let index = 0 - for (let { z } = min; z < max.z; z++) { - for (let { x } = min; x < max.x; x++) { - for (let { y } = min; y < max.y; y++) { - const xzProj = new Vector2(x, z).sub(asVect2(entityPos)) - if (xzProj.length() > 0) { - if (y < min.y + treeSize) { - // empty space around trunk between ground and trunk top - this.chunkData[index++] = BlockType.NONE - } else { - // tree foliage - const blockType = TreeGenerators[type]( - xzProj.length(), - y - (min.y + treeSize + treeRadius), - treeRadius, - ) - this.chunkData[index++] = blockType - } - } else { - // tree trunk - this.chunkData[index++] = BlockType.TREE_TRUNK - } - } - } - } - return this.chunkData - } - - getBlocksBuffer(blockPos: Vector3) { - const { chunkBox, chunkData } = this - const chunkDims = chunkBox.getSize(new Vector3()) - const chunkLocalPos = blockPos.clone().sub(chunkBox.min) - const buffIndex = - chunkLocalPos.z * chunkDims.x * chunkDims.y + - chunkLocalPos.x * chunkDims.y - const buffer = chunkData.slice(buffIndex, buffIndex + chunkDims.y) - return buffer - } - - toStub() { - const { chunkBox, chunkData, entityData } = this - const entityChunk: EntityChunkStub = { - box: chunkBox, - data: chunkData, - entity: entityData, - } - return entityChunk - } - - static fromStub(chunkStub: EntityChunkStub) { - const entityChunkData = chunkStub.data - const entityChunkBox = WorldUtils.parseThreeStub(chunkStub.box) - const entityData = chunkStub.entity as EntityData - entityData.bbox = WorldUtils.parseThreeStub(entityData.bbox) - const entityChunk = new EntityChunk(entityData, entityChunkBox) - entityChunk.chunkData = entityChunkData - return entityChunk - } -} diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index f34a62d..7a557b7 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -1,4 +1,4 @@ -import { Box2, Vector2, Vector3 } from 'three' +import { Box2, MathUtils, Vector2, Vector3 } from 'three' import { Block, PatchBlock, PatchKey } from '../common/types' import { @@ -10,7 +10,7 @@ import { import { WorldComputeProxy } from '../index' import { BlockType } from '../procgen/Biome' -import { DataContainer } from './DataContainers' +import { PatchContainer } from './PatchContainer' export enum BlockMode { DEFAULT, @@ -43,8 +43,15 @@ const BlockDataBitAllocation = { } export type BlockIteratorRes = IteratorResult - -export class GroundPatch extends DataContainer { +/** + * field | bits alloc | value range + * -----|------------|-------------------------------- + * ground elevation | 10 | 1024 + * groundIndex# | 6 | 64 + * overgroundIndex | 16 | support for 65536 different configurations + * + */ +export class GroundPatch extends PatchContainer { rawData: Uint32Array isEmpty = true @@ -162,6 +169,16 @@ export class GroundPatch extends DataContainer { // 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 + } + /** * * @param rangeBox iteration range as global coords diff --git a/src/datacontainers/OvergroundEntities.ts b/src/datacontainers/OvergroundEntities.ts new file mode 100644 index 0000000..2ebf283 --- /dev/null +++ b/src/datacontainers/OvergroundEntities.ts @@ -0,0 +1,59 @@ +import { Box2, Vector2, Vector3 } from "three"; +import { asVect2, asVect3 } from "../common/utils"; +import { ChunkContainer } from "./ChunkContainer"; +import { PseudoDistributionMap } from "./RandomDistributionMap"; + +export enum WorldObjectType { + PineTree_10_5, + AppleTree_10_5, + SpruceTree_schem, +} + +type WorldEntity = { + type: WorldObjectType + template: ChunkContainer // builder template +} + +type SpawnableEntity = { + entity: WorldEntity + spawner: PseudoDistributionMap +} +// or SpawnedEntity +export type InstancedEntity = { + entity: WorldEntity, + spawnLoc: Vector3, +} + +/** + * Voxelizable items spawning inside world + * To register object type, should be provided object's + * - voxels definition (template) + * - spawner (distribution) + */ +export class OvergroundEntities { + static registered: Record = {} + // object external builder (proc generator, schematic loader) + + static registerEntity({ entity, spawner }: SpawnableEntity) { + this.registered[entity.type] = { entity, spawner } + } + + static querySpawnedEntities(entityType: WorldObjectType, spawnRegion: Box2) { + const record = this.registered[entityType] + const { entity, spawner } = record + const entityDims = entity.template.bounds.getSize(new Vector3()) + const entityOverlapTest = (testRegion: Box2, spawnLoc: Vector2) => new Box2().setFromCenterAndSize(spawnLoc, asVect2(entityDims)).intersectsBox(testRegion) + const spawnLocations = spawner.querySpawnLocations(spawnRegion, entityOverlapTest).map(loc => asVect3(loc, 0)) + const instancedEntities: InstancedEntity[] = spawnLocations.map(spawnLoc => ({ entity, spawnLoc })) + return instancedEntities + } + + // get object's buffer at queried location, from its instance + static getInstancedEntityBuffer({ entity, spawnLoc }: InstancedEntity, queriedPos: Vector2) { + // translate queried loc to template local pos + const localPos = queriedPos.clone().sub(spawnLoc) + const buffer = entity.template.readBufferY(localPos) + return buffer + } +} + diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/PatchContainer.ts similarity index 98% rename from src/datacontainers/DataContainers.ts rename to src/datacontainers/PatchContainer.ts index 4971698..587dd56 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/PatchContainer.ts @@ -11,7 +11,7 @@ import { WorldConf } from '../index' /** * Multi purpose low level data container */ -export abstract class DataContainer { +export abstract class PatchContainer { bounds: Box2 dimensions: Vector2 margin = 0 diff --git a/src/datacontainers/WorldChunk.ts b/src/datacontainers/WorldChunk.ts deleted file mode 100644 index 1baee8c..0000000 --- a/src/datacontainers/WorldChunk.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Box3, MathUtils, Vector3 } from 'three' - -import { ChunkKey } from '../common/types' -import { ChunkFactory } from '../index' - -import { BlockData, BlockMode } from './GroundPatch' - -export type ChunkDataContainer = { - box: Box3 - data: Uint16Array -} - -export type WorldChunkStub = { - key: ChunkKey - data: Uint16Array | null -} - -export class WorldChunk { - chunkBox: Box3 - chunkData: Uint16Array - - constructor(chunkBox: Box3) { - this.chunkBox = chunkBox - const chunkDims = chunkBox.getSize(new Vector3()) - this.chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) - } - - writeBlock( - blockLocalPos: Vector3, - blockData: BlockData, - bufferOver: Uint16Array | [], - ) { - const { chunkBox, chunkData } = this - const chunk_size = chunkBox.getSize(new Vector3()).x // Math.round(Math.pow(chunkData.length, 1 / 3)) - - let written_blocks_count = 0 - - const level = MathUtils.clamp( - blockLocalPos.y + bufferOver.length, - chunkBox.min.y, - chunkBox.max.y, - ) - let buff_index = Math.max(level - blockLocalPos.y, 0) - let h = level - chunkBox.min.y // local height - // debug_mode && is_edge(local_pos.z, local_pos.x, h, patch_size - 2) - // ? BlockType.SAND - // : block_cache.type - let depth = 0 - while (h >= 0) { - const blocksIndex = - blockLocalPos.z * Math.pow(chunk_size, 2) + - h * chunk_size + - blockLocalPos.x - const groundType = blockData.type//depth > 0 && bufferOver.length === 0 ? BlockType.ROCK : blockData.type - const blockType = buff_index > 0 ? bufferOver[buff_index] : groundType - const skip = - buff_index > 0 && - chunkData[blocksIndex] !== undefined && - !bufferOver[buff_index] - if (!skip && blockType !== undefined) { - // #hack: disable block mode below ground to remove checkerboard excess - const skipBlockMode = - depth > 0 && - (bufferOver.length === 0 || bufferOver[buff_index] || buff_index < 0) - const blockMode = skipBlockMode ? BlockMode.DEFAULT : blockData.mode - chunkData[blocksIndex] = ChunkFactory.defaultInstance.voxelDataEncoder( - blockType, - blockMode, - ) - blockType && written_blocks_count++ - } - h-- - buff_index-- - depth++ - } - return written_blocks_count - } -} diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 5ca99dc..1e42e29 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -4,7 +4,7 @@ import { Block, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, - DataContainer, + PatchContainer, GroundPatch, ProcLayer, WorldComputeProxy, @@ -142,7 +142,7 @@ export class BoardContainer extends GroundPatch { } // copy content over board container this.init(finalBounds) - DataContainer.copySourceOverTargetContainer(tempContainer, this) + PatchContainer.copySourceOverTargetContainer(tempContainer, this) } async retrieveAndTrimTrees() { @@ -272,7 +272,7 @@ export class BoardContainer extends GroundPatch { } data.push(boardElement) } - // DataContainer.copySourceOverTargetContainer(boardContainer, this) + // PatchContainer.copySourceOverTargetContainer(boardContainer, this) const board: BoardOutputData = { origin, size, data } return board } diff --git a/src/index.ts b/src/index.ts index 88376cf..0a8251f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export { Biome, BlockType } from './procgen/Biome' +export { WorldObjectType, OvergroundEntities } from './datacontainers/OvergroundEntities' export { WorldConf } from './misc/WorldConfig' export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' @@ -11,9 +12,9 @@ export { BlockMode, GroundPatch } from './datacontainers/GroundPatch' export { GroundMap } from './datacontainers/GroundMap' export { ChunkFactory } from './tools/ChunkFactory' export { WorldComputeProxy } from './api/WorldComputeProxy' -export { DataContainer } from './datacontainers/DataContainers' +export { PatchContainer } from './datacontainers/PatchContainer' +export { SchematicLoader } from './tools/SchematicLoader' + export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' - -// export type { MappingConf, MappingData, MappingRanges } from "./common/types" -// export { DevHacks } from './tools/DevHacks' +export * as ProceduralGenerators from './tools/ProceduralGenerators' diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index 2e47878..267e8d5 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -1,4 +1,4 @@ -import { Vector2 } from 'three' +import { Vector2, Vector3 } from 'three' import { BlockType } from '../index' @@ -18,6 +18,10 @@ export class WorldConf { return new Vector2(this.patchSize, this.patchSize) } + static get defaultChunkDimensions() { + return new Vector3(this.patchSize, this.patchSize, this.patchSize) + } + static defaultDistMapPeriod = 4 * WorldConf.patchSize static debug = { patch: { diff --git a/src/procgen/WorldEntities.ts b/src/procgen/WorldEntities.ts deleted file mode 100644 index ff4d06c..0000000 --- a/src/procgen/WorldEntities.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Box2, Box3, Vector2 } from 'three' -import { Vector3 } from 'three/src/math/Vector3' - -import { EntityData, EntityType } from '../common/types' -import { PseudoDistributionMap } from '../index' - -// TODO remove hardcoded entity dimensions to compute from entity type -const entityDefaultDims = new Vector3(10, 20, 10) - -// TODO rename as WorldDistribution -export class WorldEntities { - // eslint-disable-next-line no-use-before-define - static singleton: WorldEntities - static get instance() { - this.singleton = this.singleton || new WorldEntities() - return this.singleton - } - - entityDistributionMapping: Record - - constructor() { - const treeDistribution = new PseudoDistributionMap() - - this.entityDistributionMapping = { - [EntityType.NONE]: treeDistribution, - [EntityType.TREE_APPLE]: treeDistribution, - [EntityType.TREE_PINE]: treeDistribution, - } - } - - queryDistributionMap(entityType: EntityType) { - const entityRadius = this.getEntityData(entityType).params.radius - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => - testRange.distanceToPoint(entityPos) <= entityRadius - const distributionMap = this.entityDistributionMapping[entityType] - const query = (bbox: Box2) => - distributionMap.querySpawnLocations(bbox, intersectsEntity) - return query - } - - getEntityData(entityType: EntityType, entityPos?: Vector3) { - // TODO custom entity shape and params from entity type - entityPos = entityPos || new Vector3() - entityPos.y = entityDefaultDims.y / 2 - const entityShape = new Box3().setFromCenterAndSize( - entityPos, - entityDefaultDims, - ) - const entityParams = { - radius: 5, - size: 10, - } - const entityData: EntityData = { - type: entityType, - bbox: entityShape, - params: entityParams, - } - return entityData // entityBox.translate(entityPos) - } -} diff --git a/src/third-party/nbt.ts b/src/third-party/nbt.ts new file mode 100644 index 0000000..84b865e --- /dev/null +++ b/src/third-party/nbt.ts @@ -0,0 +1,308 @@ +/** + * This is code from + * + * NBT.js - a JavaScript parser for NBT archives + * by Sijmen Mulder + * + * refactored to be more ES6 friendly + */ + +// var zlib = typeof require !== 'undefined' ? require('zlib') : window.zlib; + +function hasGzipHeader(data) { + var head = new Uint8Array(data.slice(0, 2)); + return head.length === 2 && head[0] === 0x1f && head[1] === 0x8b; +} + +function decodeUTF8(array: []) { + 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); +} + +/* Not all environments, in particular PhantomJS, supply + Uint8Array.slice() */ +function sliceUint8Array(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 +*/ +enum DataType { + 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" +} + +/** + * 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", +} + +const DataSizeMapping: Partial> = { + [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); + }, + [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) + } + + read(dataType: DataType) { + const dataSize = DataSizeMapping[dataType] || 0 + const callee = 'get' + DataTypeMapping[dataType] + var val = dataType !== DataType.END ? this.dataView[callee](this.offset) : ''; + this.offset += dataSize; + return val; + } + + /** + * @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) { + if (!data) { throw new Error('Argument "data" is falsy'); } + + var reader = new NBTReader(data) + + // var type = reader.byte(); + var type = reader.dataTypeHandler[DataType.BYTE]() + if (type !== DataType.COMPOUND) { + throw new Error('Top tag should be a compound'); + } + + return { + name: reader.dataTypeHandler[DataType.STRING](), + value: reader.dataTypeHandler[DataType.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 */ + + /** + * 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, callback) { + + if (!hasGzipHeader(data)) { + callback(null, NBTReader.parseUncompressed(data)); + } else if (!zlib) { + callback(new Error('NBT archive is compressed but zlib is not ' + + 'available'), null); + } else { + /* zlib.gunzip take a Buffer, at least in Node, so try to convert + if possible. */ + var buffer; + if (data.length) { + buffer = data; + } else if (typeof Buffer !== 'undefined') { + buffer = new Buffer(data); + } else { + /* In the browser? Unknown zlib library. Let's settle for + Uint8Array and see what happens. */ + buffer = new Uint8Array(data); + } + + zlib.gunzip(buffer, function (error, uncompressed) { + if (error) { + callback(error, null); + } else { + callback(null, NBTReader.parseUncompressed(uncompressed)); + } + }); + } + }; + +} \ No newline at end of file diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index e5e9241..de20c70 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,14 +1,15 @@ -import { Vector3 } from 'three' +import { MathUtils, Vector2, Vector3 } from 'three' import { PatchId } from '../common/types' import { asBox2, + asVect2, asVect3, chunkBoxFromId, serializeChunkId, } from '../common/utils' -import { EntityChunk } from '../datacontainers/EntityChunk' -import { WorldChunk, WorldChunkStub } from '../datacontainers/WorldChunk' +import { ChunkContainer } from '../datacontainers/ChunkContainer' +import { InstancedEntity } from '../datacontainers/OvergroundEntities' import { BlockMode, BlockType, GroundPatch, WorldConf } from '../index' // for debug use only @@ -52,60 +53,89 @@ export class ChunkFactory { } /** - * chunkify or chunksAssembly - * Assembles world building blocks (GroundPatch, EntityChunk) together - * to form final world chunk + * Assembles layers together: ground, world objects */ - chunkify(patch: GroundPatch, patchEntities: EntityChunk[]) { - const patchChunkIds = patch.id - ? ChunkFactory.default.genChunksIdsFromPatchId(patch.id) + chunkifyPatch(groundPatchLayer: GroundPatch, overgroundEntities: InstancedEntity[]) { + const patchChunkIds = groundPatchLayer.id + ? ChunkFactory.default.genChunksIdsFromPatchId(groundPatchLayer.id) : [] const worldChunksStubs = patchChunkIds.map(chunkId => { const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) - const worldChunk = new WorldChunk(chunkBox) - // Ground pass - this.mergeGroundBlocks(worldChunk, patch) - // Entities pass - this.mergePatchEntities(worldChunk, patch, patchEntities) - const worldChunkStub: WorldChunkStub = { + const worldChunk = new ChunkContainer(chunkBox) + // this.mergeGroundBlocks(worldChunk, patch) + // this.mergePatchEntities(worldChunk, patch, worldObjects) + this.mergePatchLayersToChunk(worldChunk, groundPatchLayer, overgroundEntities) + const worldChunkStub = { key: serializeChunkId(chunkId), - data: worldChunk.chunkData, + data: worldChunk.rawData, } return worldChunkStub }) return worldChunksStubs } - mergeGroundBlocks(worldChunk: WorldChunk, patch: GroundPatch) { - const blocks = patch.iterBlocksQuery(undefined, false) + mergePatchLayersToChunk(chunkContainer: ChunkContainer, groundPatchLayer: GroundPatch, overgroundEntities: InstancedEntity[]) { + this.mergeGroundLayer(chunkContainer, groundPatchLayer) + this.mergePatchEntities(chunkContainer, groundPatchLayer, overgroundEntities) + } + + mergeLayersBuffers(){ + + } + + mergeOverlappingBuffers(){ + + } + + + mergeGroundLayer(worldChunk: ChunkContainer, patchGroundLayer: GroundPatch) { + const ymin = worldChunk.bounds.min.y + const ymax = worldChunk.bounds.max.y + const blocks = patchGroundLayer.iterBlocksQuery(undefined, false) for (const block of blocks) { - const blockData = block.data - const blockType = block.data.type const blockLocalPos = block.localPos as Vector3 blockLocalPos.x += 1 // block.localPos.y = patch.bbox.max.y blockLocalPos.z += 1 - blockData.type = - highlightPatchBorders(blockLocalPos, blockType) || blockType - worldChunk.writeBlock(blockLocalPos, blockData, block.buffer || []) + const blockType = highlightPatchBorders(blockLocalPos, block.data.type) || block.data.type + const blockMode = block.data.mode + // generate ground buffer + let bufferCount = MathUtils.clamp(block.data.level - ymin, 0, ymax - ymin) + const groundBuffer = []; + while (bufferCount > 0) { + const rawData = this.voxelDataEncoder( + blockType, + blockMode, + ) + groundBuffer.push(rawData) + bufferCount-- + } + + // worldChunk.writeSector() + worldChunk.writeBuffer(blockLocalPos, block.data, []) } } + mergePatchEntities( - worldChunk: WorldChunk, - patch: GroundPatch, - patchEntities: EntityChunk[], + worldChunk: ChunkContainer, + groundLayer: GroundPatch, + overgroundEntities: InstancedEntity[], ) { - patchEntities.forEach(entityChunk => { + overgroundEntities.forEach(instancedEntity => { + const { spawnLoc, entity } = instancedEntity + const entityBounds = asBox2(entity.template.bounds).translate(asVect2(spawnLoc)) + const center = entityBounds.getCenter(new Vector2()).floor() + spawnLoc.y = groundLayer.getBlock(center)?.pos.y || 0 // return overlapping blocks between entity and container - const patchBlocksIter = patch.iterBlocksQuery( - asBox2(entityChunk.chunkBox), - ) + const patchBlocksIter = groundLayer.iterBlocksQuery(entityBounds) // iter over entity blocks for (const block of patchBlocksIter) { - // const buffer = entityChunk.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) - let bufferData = entityChunk.getBlocksBuffer(block.pos) - const buffOffset = entityChunk.chunkBox.min.y - block.pos.y + // const buffer = instancedEntity.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) + // translate queried loc to template local pos + const localPos = block.pos.clone().sub(spawnLoc) + let bufferData = entity.template.readBufferY(asVect2(localPos)) + const buffOffset = spawnLoc.y - block.pos.y const buffSrc = Math.abs(Math.min(0, buffOffset)) const buffDest = Math.max(buffOffset, 0) bufferData = bufferData.copyWithin(buffDest, buffSrc) @@ -115,7 +145,7 @@ export class ChunkFactory { : bufferData block.localPos.x += 1 block.localPos.z += 1 - worldChunk.writeBlock(block.localPos, block.data, bufferData) + worldChunk.writeBuffer(block.localPos, block.data, bufferData) } }) } diff --git a/src/tools/ProceduralGenerators.ts b/src/tools/ProceduralGenerators.ts new file mode 100644 index 0000000..f4edb3e --- /dev/null +++ b/src/tools/ProceduralGenerators.ts @@ -0,0 +1,91 @@ +import { Vector3, Vector2, Box3 } from 'three' +import { asVect2 } from '../common/utils' +import { ChunkContainer } from '../datacontainers/ChunkContainer' +import { BlockType, PseudoDistributionMap } from '../index' + +export type TreeDef = { + // type: TreeKind, + size: number, + radius: number +} + +export abstract class ProceduralTree { + // type + size + radius + + constructor(size: number, radius: number) { + // super(new Box3(new Vector3(), new Vector3(2 * radius, size, 2 * radius))) + this.size = size + this.radius = radius + // this.type = type + } + + abstract generate(xzProj: number, y: number, range: number): BlockType; + + // get key() { + // const { size, radius, type } = this + // const treeDef = { + // size, radius, type + // } + // return treeKey(treeDef) + // } + + voxelize() { + const { size: treeSize, radius: treeRadius } = this + const treeBounds = new Box3(new Vector3(), new Vector3(2 * treeRadius, treeSize, 2 * treeRadius)) + const treeChunk = new ChunkContainer(treeBounds) + const entityPos = treeBounds.getCenter(new Vector3()) + const { min, max } = treeBounds + let index = 0 + const chunkIter = treeChunk.iterateContent() + for (const chunkBlock of chunkIter) { + const { x, y, z } = chunkBlock.localPos + const xzProj = new Vector2(x, z).sub(asVect2(entityPos)) + if (xzProj.length() > 0) { + if (y < min.y + treeSize) { + // empty space around trunk between ground and trunk top + treeChunk.rawData[index++] = BlockType.NONE + } else { + // tree foliage + const blockType = this.generate( + xzProj.length(), + y - (min.y + treeSize + treeRadius), + treeRadius, + ) + treeChunk.rawData[index++] = blockType + } + } else { + // tree trunk + treeChunk.rawData[index++] = BlockType.TREE_TRUNK + } + } + } +} + +export class AppleTree extends ProceduralTree { + distribution: PseudoDistributionMap + constructor(size: number, radius: number) { + super(size, radius) + this.distribution = new PseudoDistributionMap() + } + generate(xzProj: number, y: number, range: number): BlockType { + const dist = Math.sqrt(Math.pow(xzProj, 2) + Math.pow(y, 2)) + const isFoliage = dist <= range + return isFoliage ? BlockType.TREE_FOLIAGE : BlockType.NONE + } + +} + +export class PineTree extends ProceduralTree { + distribution: PseudoDistributionMap + constructor(size: number, radius: number) { + super(size, radius) + this.distribution = new PseudoDistributionMap() + } + generate(xzProj: number, y: number, range: number): BlockType { + const dist = xzProj // xzProj*(y+radius) + const isFoliage = dist <= range * (1 - (0.35 * (y + range)) / range) + return isFoliage ? BlockType.TREE_FOLIAGE_2 : BlockType.NONE + } +} \ No newline at end of file diff --git a/src/tools/SchematicLoader.ts b/src/tools/SchematicLoader.ts new file mode 100644 index 0000000..5dddebf --- /dev/null +++ b/src/tools/SchematicLoader.ts @@ -0,0 +1,132 @@ +import { NBTReader } from "../third-party/nbt"; +import Pako from "pako" +import { Box3, Vector3 } from "three"; +import { BlockType } from "../procgen/Biome"; +import { ChunkContainer } from "../datacontainers/ChunkContainer"; + +export class SchematicLoader { + + static async load(path: string) { + // const schem = await Schematic.read(Buffer.from(schemData), '1.16.4') + const res = await fetch(path); + console.log(res); + 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 parse(rawData) { + return new Promise((resolve, reject) => { + NBTReader.parse(rawData, function (error, data) { + if (error) { throw error; } + resolve(data); + }); + }); + } + + /** + * convert schematic format to world object + * @param schemBlocks + * @returns + */ + static async createChunkContainer(fileUrl: string) { + const toWorldBlockType = (rawType: string) => { + switch (rawType) { + case "air": + return BlockType.NONE + case "spruce_log": + return BlockType.TREE_TRUNK + case "spruce_leaves": + return BlockType.TREE_FOLIAGE + default: + console.log(rawType) + return BlockType.NONE + } + } + const rawData = await SchematicLoader.load(fileUrl) + const parsedData = await SchematicLoader.parse(rawData) + const schemBlocks = SchematicLoader.getBlocks(parsedData) + const dims = new Vector3(schemBlocks[0].length, schemBlocks.length, schemBlocks[0][0].length) + const orig = new Vector3(0, 140, 0) + const end = orig.clone().add(dims) + const bbox = new Box3(orig, end) + const chunkContainer = new ChunkContainer(bbox) + let index = 0 + 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] + const blockType = toWorldBlockType(rawType) + // worldObj.rawData[index++] = blockType + const localPos = new Vector3(x, y, z) + const blockIndex = chunkContainer.getIndex(localPos) + chunkContainer.rawData[blockIndex] = blockType + } + } + } + return chunkContainer + } + + + static getBlocks(schemData) { + // 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; + + // Create a new 3d array + let skippedBlocks = []; + let blocks = []; + 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; + } + + static getBlockData(palette, blockId) { + // Iterate through each key pair in the palette values + for (const [key, value] of Object.entries(palette)) { + if (value.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, + }; + } + } + return { + name: "minecraft:air", + }; + } +} \ No newline at end of file diff --git a/src/tools/TreeGenerator.ts b/src/tools/TreeGenerator.ts deleted file mode 100644 index 6abb69f..0000000 --- a/src/tools/TreeGenerator.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { BlockType, EntityType } from '../index' - -export type TreeGenerator = ( - xzProj: number, - y: number, - range: number, -) => BlockType - -const AppleTreeGen = (xzProj: number, y: number, range: number) => { - const dist = Math.sqrt(Math.pow(xzProj, 2) + Math.pow(y, 2)) - const isFoliage = dist <= range - return isFoliage ? BlockType.TREE_FOLIAGE : BlockType.NONE -} - -const PineTreeGen = (xzProj: number, y: number, range: number) => { - const dist = xzProj // xzProj*(y+radius) - const isFoliage = dist <= range * (1 - (0.35 * (y + range)) / range) - return isFoliage ? BlockType.TREE_FOLIAGE_2 : BlockType.NONE -} - -export const TreeGenerators: Record = { - [EntityType.NONE]: () => BlockType.NONE, - [EntityType.TREE_APPLE]: AppleTreeGen, - [EntityType.TREE_PINE]: PineTreeGen, -} From 221594130680b141643d105e12d532c6a9016c75 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 2 Oct 2024 07:23:19 +0000 Subject: [PATCH 03/13] feat: biome compute optims, use bilinear interpolation --- src/api/world-compute.ts | 31 ++++++++------ src/common/utils.ts | 89 ++++++++++++++++++++++++++++++++-------- src/procgen/Biome.ts | 6 +-- src/procgen/Heightmap.ts | 10 ++--- 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index e9b2f02..842d514 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,10 +1,10 @@ import { Box2, Vector2, Vector3 } from 'three' import { EntityType, GroundPatch } from '../index' -import { Biome, BlockType } from '../procgen/Biome' +import { Biome, BiomeInfluence, BiomeType, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { Block, EntityData, PatchKey } from '../common/types' -import { asBox3, asVect2, asVect3 } from '../common/utils' +import { Block, EntityData, NoiseLevelConf, PatchKey } from '../common/types' +import { asBox3, asVect2, asVect3, bilinearInterpolation, getBoundsCornerPoints } from '../common/utils' import { BlockData } from '../datacontainers/GroundPatch' import { OvergroundEntities, WorldObjectType } from '../datacontainers/OvergroundEntities' // import { BoardInputParams } from '../feats/BoardContainer' @@ -58,9 +58,10 @@ export const computeBlocksBatch = ( return blocksBatch } -export const computeGroundBlock = (blockPos: Vector3) => { - const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) - const biomeType = Biome.instance.getBiomeType(biomeContribs) +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 NoiseLevelConf const currLevelConf = noiseLevel.data @@ -71,7 +72,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { const level = Heightmap.instance.getGroundLevel( blockPos, rawVal, - biomeContribs, + biomeInfluence, ) // const pos = new Vector3(blockPos.x, level, blockPos.z) const variation = Biome.instance.posRandomizer.eval(blockPos.clone().multiplyScalar(50))//Math.cos(0.1 * blockPos.length()) / 100 @@ -87,8 +88,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { if (!type) { console.log(currLevelConf) } - const type = blockTypes.grounds[0] as BlockType - const spawnableItems = blockTypes.entities + const spawnableItems = currLevelConf.entities // const entityType = blockTypes.entities?.[0] as EntityType // let offset = 0 // if (lastBlock && entityType) { @@ -107,8 +107,13 @@ export const computeGroundBlock = (blockPos: Vector3) => { export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { const groundPatch = new GroundPatch(boundsOrPatchKey) // eval biome at patch corners - const patchCorners = getBoundsCornerPoints(groundPatch.bounds) - // patchCorners.map(point=>Biome.instance.getBiomeInfluence()) + const equals = (v1, v2) => { + const different = Object.keys(v1).find(k => v1[k] !== v2[k]) + return !different + } + const [p11, p12, p21, p22] = getBoundsCornerPoints(groundPatch.bounds) + const [v11, v12, v21, v22] = [p11, p12, p21, p22].map(point => Biome.instance.getBiomeInfluence(point)) + const allEquals = equals(v11, v12) && equals(v11, v21) && equals(v11, v22) const { min, max } = groundPatch.bounds const blocks = groundPatch.iterBlocksQuery(undefined, false) const level = { @@ -117,7 +122,9 @@ export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { } let blockIndex = 0 for (const block of blocks) { - const blockData = computeGroundBlock(block.pos) + // if biome is the same at each patch corners, no need tp interpolate + const interpolatedBiome = allEquals ? v11 : bilinearInterpolation(asVect2(block.pos), groundPatch.bounds, { v11, v12, v21, v22 }) + const blockData = computeGroundBlock(block.pos, interpolatedBiome) level.min = Math.min(min.y, blockData.level) level.max = Math.max(max.y, blockData.level) groundPatch.writeBlockData(blockIndex, blockData) diff --git a/src/common/utils.ts b/src/common/utils.ts index ded0f78..53276cf 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -49,16 +49,73 @@ const findMatchingRange = (inputVal: number, noiseMappings: NoiseLevelConf) => { } /** - * - * @param p1 - * @param p2 - * @param t time between P1 and P2 + * | | + * y2 --+-------+-- + * | + P | + * | | + * y1 --+-------+-- + * | | + * x1 x2 + * @param p + * @param p11 + * @param p12 + * @param p22 + * @param p21 + * @returns + */ +const bilinearInterpolation = (p: Vector2, bounds: Box2, { v11, v12, v21, v22 }: 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 add = (...items) => { + const res: any = {} + const [first] = items + Object.keys(first).forEach(k => res[k] = sumComponents(k, items)) + return res + } + const mul = (w: number, v: any) => { + const res = { ...v } + Object.keys(res).forEach(k => res[k] *= 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, v11) + const m12 = mul(w12, v12) + const m21 = mul(w21, v21) + const m22 = mul(w22, v22) + const res = add(m11, m12, m21, m22) + return res +} + +/** + * Inverse distance weighting (IDW) + * @param cornersPoints + * @param point */ -const interpolatePoints = (p1: Vector2, p2: Vector2, t: number) => { - // interpolate - const range: Vector2 = p2.clone().sub(p1) - const slope = range.x > 0 ? range.y / range.x : 0 - return p1.y + slope * (t - p1.x) +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 } /** @@ -195,12 +252,12 @@ const getNeighbours3D = ( } const getBoundsCornerPoints = (bounds: Box2) => { - const { min, max } = bounds - const xMyP = min.clone() - xMyP.y = max.y - const xPyM = min.clone() - xPyM.x = max.x - const points = [min, max, xMyP, xPyM] + const { min: xMyM, max: xPyP } = bounds + const xMyP = xMyM.clone() + xMyP.y = xPyP.y + const xPyM = xMyM.clone() + xPyM.x = xPyP.x + const points = [xMyM, xMyP, xPyM, xPyP] return points } @@ -406,7 +463,7 @@ export { vectRoundToDec, clamp, findMatchingRange, - interpolatePoints, + bilinearInterpolation, getNeighbours2D, getNeighbours3D, bboxContainsPointXZ, diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 1ff40d5..bf890dd 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -301,7 +301,7 @@ export class Biome { ) => { const { seaLevel } = this.params rawVal = includeSea ? Math.max(rawVal, seaLevel) : rawVal - const validInput = Utils.clamp(rawVal, 0, 1) + rawVal = Utils.clamp(rawVal, 0, 1) const mappingRange = Utils.findMatchingRange( rawVal, this.mappings[biomeType], @@ -309,8 +309,8 @@ export class Biome { 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 interpolated = Utils.interpolatePoints(min, max, validInput) - return interpolated // includeSea ? Math.max(interpolated, seaLevel) : interpolated + const lerp = min.lerp(max, (rawVal - min.x) / (max.x - min.x)) + return lerp.y // includeSea ? Math.max(interpolated, seaLevel) : interpolated } getBlockLevelInterpolated = ( diff --git a/src/procgen/Heightmap.ts b/src/procgen/Heightmap.ts index dafd422..525d0b5 100644 --- a/src/procgen/Heightmap.ts +++ b/src/procgen/Heightmap.ts @@ -74,11 +74,11 @@ export class Heightmap { 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( - // rawVal, - // biomeInfluence, - // ) - const initialVal = Biome.instance.getBlockLevel(rawVal, Biome.instance.getBiomeType(biomeInfluence)) + const initialVal = Biome.instance.getBlockLevelInterpolated( + rawVal, + biomeInfluence, + ) + // const initialVal = Biome.instance.getBlockLevel(rawVal, Biome.instance.getBiomeType(biomeInfluence)) const finalVal = this.applyModulation( blockPos, initialVal, From 23f6479e1f2c25bf217264ade7895ab170692608 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 4 Oct 2024 14:54:07 +0000 Subject: [PATCH 04/13] feat: refactor overground items, improve buffers merge with ground, external schematic config --- .gitignore | 5 +- src/api/WorldComputeProxy.ts | 68 +++++------ src/api/world-compute.ts | 148 ++++++++++------------- src/datacontainers/ChunkContainer.ts | 140 ++++++--------------- src/datacontainers/OvergroundEntities.ts | 32 +++-- src/index.ts | 2 +- src/misc/WorldConfig.ts | 6 + src/procgen/Biome.ts | 2 +- src/tools/ChunkFactory.ts | 106 ++++++---------- src/tools/SchematicLoader.ts | 34 +++--- 10 files changed, 214 insertions(+), 329 deletions(-) diff --git a/.gitignore b/.gitignore index 77c7d6d..54058cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules -.env types dist/ -.parcel-cache +.env +.discarded +.todo.md \ No newline at end of file diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index ca21fea..9f525fa 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,16 +1,12 @@ import { Box2, Vector3 } from 'three' -import { Block, EntityData, PatchKey } from '../common/types' -import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' +import { Block, PatchKey } from '../common/types' import { GroundPatch, WorldCompute, WorldUtils } from '../index' -import { parseThreeStub } from '../common/utils' export enum ComputeApiCall { - PatchCompute = 'bakeGroundPatch', + PatchCompute = 'bakePatch', BlocksBatchCompute = 'computeBlocksBatch', - OvergroundBufferCompute = 'computeOvergroundBuffer', - QueryEntities = 'queryEntities', - BakeEntities = 'queryBakeEntities', + OvergroundItemsQuery = 'retrieveOvergroundItems', BattleBoardCompute = 'computeBoardData', } @@ -21,8 +17,9 @@ export type ComputeApiParams = Partial<{ }> /** - * Exposing world compute api with ability to run inside optional worker - * When provided all request are proxied to worker instead of main thread + * Frontend exposing world APIs and 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 { // eslint-disable-next-line no-use-before-define @@ -81,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: Block[]) => + // parse worker's data to recreate original objects + blocksStubs.map(blockStub => { + blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos) + return blockStub + }), + )) as Block[]) return blocks } @@ -101,31 +98,26 @@ export class WorldComputeProxy { // } // } - async queryEntities(queriedRegion: Box2) { - // const entitiesData = !this.worker - // ? WorldCompute.queryEntities(queriedRegion) - // : ((await this.workerCall( - // ComputeApiCall.QueryEntities, - // [queriedRegion], // [emptyPatch.bbox] - // )?.then(stubs => - // stubs.map((stub: EntityData) => ({ - // ...stub, - // bbox: parseThreeStub(stub.bbox), - // })), - // )) as EntityData[]) - return [] //entitiesData + async queryOvergroundItems(queriedRegion: Box2) { + const overgroundItems = !this.worker + ? WorldCompute.retrieveOvergroundItems(queriedRegion) + : await this.workerCall( + ComputeApiCall.OvergroundItemsQuery, + [queriedRegion], // [emptyPatch.bbox] + ) + return overgroundItems } async *iterPatchCompute(patchKeysBatch: PatchKey[]) { for (const patchKey of patchKeysBatch) { const patch = !this.worker - ? WorldCompute.bakeGroundPatch(patchKey) + ? 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 } @@ -133,7 +125,7 @@ export class WorldComputeProxy { async bakeGroundPatch(boundsOrPatchKey: Box2 | string) { const patchStub = !this.worker - ? WorldCompute.bakeGroundPatch(boundsOrPatchKey) + ? WorldCompute.bakePatch(boundsOrPatchKey) : await this.workerCall(ComputeApiCall.PatchCompute, [boundsOrPatchKey]) // ?.then(patchStub => new GroundPatch().fromStub(patchStub)) as GroundPatch diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 842d514..fa308e1 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,14 +1,18 @@ import { Box2, Vector2, Vector3 } from 'three' -import { EntityType, GroundPatch } from '../index' -import { Biome, BiomeInfluence, BiomeType, BlockType } from '../procgen/Biome' +import { EntityType, GroundPatch, WorldConf } from '../index' +import { Biome, BiomeInfluence, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { Block, EntityData, NoiseLevelConf, PatchKey } from '../common/types' -import { asBox3, asVect2, asVect3, bilinearInterpolation, getBoundsCornerPoints } from '../common/utils' -import { BlockData } from '../datacontainers/GroundPatch' -import { OvergroundEntities, WorldObjectType } from '../datacontainers/OvergroundEntities' +import { BiomeConfKey, Block, EntityData, NoiseLevelConf, PatchKey } from '../common/types' +import { asVect2, asVect3, bilinearInterpolation, getBoundsCornerPoints } from '../common/utils' +import { BlockConfigs, BlockData } from '../datacontainers/GroundPatch' +import { OvergroundEntities, WorldItem } from '../datacontainers/OvergroundEntities' // import { BoardInputParams } from '../feats/BoardContainer' +/** + * Brain of the world which can be run in separate worker + */ + /** * Individual blocks requests */ @@ -58,7 +62,7 @@ export const computeBlocksBatch = ( return blocksBatch } -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) @@ -67,6 +71,7 @@ export const computeGroundBlock = (blockPos: Vector3, biomeInfluence: BiomeInflu const currLevelConf = noiseLevel.data const prevLevelConf = noiseLevel.prev?.data const nextLevelConf = noiseLevel.next?.data + const confKey = currLevelConf.key const confIndex = Biome.instance.getConfIndex(currLevelConf.key) // const confData = Biome.instance.indexedConf.get(confIndex) const level = Heightmap.instance.getGroundLevel( @@ -95,16 +100,24 @@ export const computeGroundBlock = (blockPos: Vector3, biomeInfluence: BiomeInflu // } // level += offset - const block: BlockData = { level, type, spawnableItems } - return block + const output = { level, type, spawnableItems, confKey } + return output } /** * Patch requests */ +export const bakePatch = (boundsOrPatchKey: PatchKey | Box2) => { + // compute patch layers + const groundLayer = bakeGroundLayer(boundsOrPatchKey) + // const overgroundItems = retrieveOvergroundItems(groundLayer.bounds) + // console.log(overgroundItems) + return groundLayer +} + // Patch ground layer -export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { +export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { const groundPatch = new GroundPatch(boundsOrPatchKey) // eval biome at patch corners const equals = (v1, v2) => { @@ -112,7 +125,11 @@ export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { return !different } const [p11, p12, p21, p22] = getBoundsCornerPoints(groundPatch.bounds) - const [v11, v12, v21, v22] = [p11, p12, p21, p22].map(point => Biome.instance.getBiomeInfluence(point)) + const [v11, v12, v21, v22] = [p11, p12, p21, p22].map(pos => { + const biomeInfluence = Biome.instance.getBiomeInfluence(pos) + // const block = computeGroundBlock(asVect3(pos), biomeInfluence) + return biomeInfluence + }) const allEquals = equals(v11, v12) && equals(v11, v21) && equals(v11, v22) const { min, max } = groundPatch.bounds const blocks = groundPatch.iterBlocksQuery(undefined, false) @@ -122,17 +139,46 @@ export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { } let blockIndex = 0 for (const block of blocks) { - // if biome is the same at each patch corners, no need tp interpolate - const interpolatedBiome = allEquals ? v11 : bilinearInterpolation(asVect2(block.pos), groundPatch.bounds, { v11, v12, v21, v22 }) - const blockData = computeGroundBlock(block.pos, interpolatedBiome) + // EXPERIMENTAL: is it faster to perform bilinear interpolation rather than sampling biome for each block? + const getBlockBiome = () => WorldConf.settings.useBiomeBilinearInterpolation && bilinearInterpolation(asVect2(block.pos), groundPatch.bounds, { v11, v12, v21, v22 }) + // if biome is the same at each patch corners, no need to interpolate + const blockData = computeGroundBlock(block.pos, allEquals ? v11 : getBlockBiome()) level.min = Math.min(min.y, blockData.level) level.max = Math.max(max.y, blockData.level) groundPatch.writeBlockData(blockIndex, blockData) + groundPatch.blockConfigs[blockData.confKey] = true blockIndex++ } return groundPatch } +export const retrieveOvergroundItems = (bounds: Box2) => { + // spawnable items based on soil type found in this specific region + const blockConfigs: any = {} + const [p11, p12, p21, p22] = getBoundsCornerPoints(bounds); + [p11, p12, p21, p22].forEach(pos => { + const block = computeGroundBlock(asVect3(pos)) + blockConfigs[block.confKey] = Biome.instance.indexedConf.get(block.confKey)?.data + }) + const spawnedItems = {} + Object.values(blockConfigs).forEach(blockConf => blockConf?.entities?.forEach(itemType => spawnedItems[itemType] = [])) + Object.keys(spawnedItems).forEach(type => { + const itemType = parseInt(type) as WorldItem + const spawnablePlaces = OvergroundEntities.querySpawnedEntities( + itemType, + bounds, + ) + spawnablePlaces.forEach(itemPos => { + // confirm entities and add spawn elevation + const block = computeGroundBlock(asVect3(itemPos)) + const blockConf = Biome.instance.indexedConf.get(block.confKey)?.data + if (blockConf?.entities?.find(val => val === itemType)) + spawnedItems[itemType].push(asVect3(itemPos, block.level)) + }) + }) + return spawnedItems +} + /** * patch is an assembly of several layers * - ground @@ -142,7 +188,7 @@ export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { export const bakePatchLayers = () => { } export const bakePatchGroundLayer = () => { } export const bakePatchUndergroundLayer = () => { } // or caverns -export const bakePatchOvergroundLayer = (boundsOrPatchKey: PatchKey | Box2, objectType: WorldObjectType) => { } +export const bakePatchOvergroundLayer = (boundsOrPatchKey: PatchKey | Box2, itemType: WorldItem) => { } // Battle board @@ -153,75 +199,3 @@ export const bakePatchOvergroundLayer = (boundsOrPatchKey: PatchKey | Box2, obje // const boardStub = boardMap.toStub() // return boardStub // } - -/** - * Entity queries/baking - */ - -export const queryEntities = (queriedRegion: Box2, queriedObjType: WorldObjectType) => { - const queriedObject = OvergroundObjects.getObjectInstance(queriedObjType) - - // const spawnablePlaces = queriedObject.queryDistribution(queriedRegion) - const spawnablePlaces = WorldEntities.instance.queryDistributionMap( - EntityType.TREE_APPLE, - )(queriedRegion) - const spawnedEntities = spawnablePlaces - .map(entLoc => - WorldEntities.instance.getEntityData( - EntityType.TREE_PINE, - asVect3(entLoc), - ), - ) - .filter(entity => confirmFinalizeEntity(entity)) - return spawnedEntities -} - -/** - * - * @param entityPos - * @returns - */ -const confirmFinalizeEntity = (entity: EntityData) => { - const entityPos = entity.bbox.getCenter(new Vector3()) - // use global coords in case entity center is from adjacent patch - const rawVal = Heightmap.instance.getRawVal(entityPos) - const biomeType = Biome.instance.getBiomeType(entityPos) - const biomeConf = Biome.instance.getBiomeConf(rawVal, biomeType) - const [entityType] = biomeConf.data.entities || [] - // confirm this kind of entity can spawn over here - if (entityType) { - entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos, rawVal) - entity.bbox.max.y += entity.bbox.min.y - return entity - } - return null -} - -export const queryBakeEntities = (queriedRange: Box2) => { - const entitiesData = queryEntities(queriedRange) - return bakeEntitiesBatch(entitiesData) -} - -export const bakeEntitiesBatch = (entities: EntityData[]) => { - // const entitiesChunks: EntityChunkStub[] = entities - // .map(entityData => new EntityChunk(entityData)) - // .map(entityChunk => { - // entityChunk.voxelize() - // return entityChunk.toStub() - // }) - return [];//entitiesChunks -} - -// /** -// * return all entity types which can spwawn over specific region -// */ -// const getSpawnableEntities = (region: Box2) => { -// // TODO -// } - -// /** -// * granular check in transition place (spline or biome transitions) -// */ -// const confirmSpawnability = () => { - -// } diff --git a/src/datacontainers/ChunkContainer.ts b/src/datacontainers/ChunkContainer.ts index c9330b5..0ef6cdd 100644 --- a/src/datacontainers/ChunkContainer.ts +++ b/src/datacontainers/ChunkContainer.ts @@ -2,8 +2,6 @@ import { Vector2, Box2, Box3, Vector3, MathUtils } from 'three' import { ChunkId, ChunkKey } from '../common/types' import { asVect3, chunkBoxFromKey, parseChunkKey, serializeChunkId } from '../common/utils' import { WorldConf } from '../misc/WorldConfig' -import { ChunkFactory } from '../tools/ChunkFactory' -import { BlockData, BlockMode } from './GroundPatch' enum ChunkAxisOrder { ZXY, @@ -22,7 +20,7 @@ export class ChunkContainer { rawData: Uint16Array axisOrder: ChunkAxisOrder - constructor(boundsOrChunkKey: Box3 | ChunkKey = new Box3(), margin = 0, axisOrder= ChunkAxisOrder.ZXY) { + constructor(boundsOrChunkKey: Box3 | ChunkKey = new Box3(), margin = 0, axisOrder = ChunkAxisOrder.ZXY) { //, bitLength = BitLength.Uint16) { const bounds = boundsOrChunkKey instanceof Box3 @@ -74,38 +72,42 @@ export class ChunkContainer { this.dimensions = bounds.getSize(new Vector3()) } - // copy occurs only on the overlapping global pos region of both containers - static copySourceOverTargetContainer(source: any, target: any) { - const adjustOverlapMargins = (overlap: Box2) => { - const margin = Math.min(target.margin, source.margin) || 0 - overlap.min.x -= target.bounds.min.x === overlap.min.x ? margin : 0 - overlap.min.y -= target.bounds.min.y === overlap.min.y ? margin : 0 - overlap.max.x += target.bounds.max.x === overlap.max.x ? margin : 0 - overlap.max.y += target.bounds.max.y === overlap.max.y ? 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 (source.bounds.intersectsBox(target.bounds)) { - const overlap = target.bounds.clone().intersect(source.bounds) + + if (sourceChunk.bounds.intersectsBox(targetChunk.bounds)) { + const overlap = targetChunk.bounds.clone().intersect(sourceChunk.bounds) adjustOverlapMargins(overlap) - for (let { y } = overlap.min; y < overlap.max.y; y++) { - // const globalStartPos = new Vector3(x, 0, overlap.min.y) - const globalStartPos = new Vector2(overlap.min.x, y) - const targetLocalStartPos = target.toLocalPos(globalStartPos) - const sourceLocalStartPos = source.toLocalPos(globalStartPos) - let targetIndex = target.getIndex(targetLocalStartPos) - let sourceIndex = source.getIndex(sourceLocalStartPos) + + for (let { z } = overlap.min; z < overlap.max.z; z++) { for (let { x } = overlap.min; x < overlap.max.x; x++) { - const sourceVal = source.rawData[sourceIndex] - if (sourceVal) { - target.rawData[targetIndex] = sourceVal + 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++ } - sourceIndex++ - targetIndex++ } } } } - /** * * @param localPos queried buffer location as Vector2 or Vector3 @@ -259,92 +261,18 @@ export class ChunkContainer { this.rawData[sectorIndex] = this.encodeSectorData(sectorData) } - readBufferY(localPos: Vector2) { - // const localPos = this.toLocalPos(asVect3(pos, this.bounds.min.y)) + readBuffer(localPos: Vector2) { const buffIndex = this.getIndex(localPos) - // const buffIndex = - // chunkLocalPos.z * chunkDims.x * chunkDims.y + - // chunkLocalPos.x * chunkDims.y const rawBuffer = this.rawData.slice(buffIndex, buffIndex + this.dimensions.y) return rawBuffer } - writeBufferY(pos: Vector2, blocksBuffer: number[]) { - const localPos = this.toLocalPos(asVect3(pos, this.bounds.min.y)) - const {extendedDims}=this - //const startOffset = this.getIndex(localPos) - //this.rawData.set(buffer, startOffset) - while(blocksBuffer.length>0){ - const index = localPos.z * extendedDims.z + - blocksBuffer.length * extendedDims.y + - localPos.x - const blockData = blocksBuffer.pop() - this.rawData[index] = ChunkFactory.defaultInstance.voxelDataEncoder( - blockData.type, - blockData.mode, - ) - } - } - - /** - * From native world format (z,x,y) to (z,y,x) format - */ - exportAsZYXFormat() { - const dimensions = this.bounds.getSize(new Vector3()) - const getTargetIndex = (localPos: Vector3) => localPos.z * dimensions.x * dimensions.y + - localPos.y * dimensions.x + - localPos.x - // read yBuffer at z,x pos - } - writeBuffer( - blockLocalPos: Vector3, - blockData: BlockData, - bufferOver: Uint16Array | [], + localPos: Vector2, + buffer: Uint16Array, ) { - const { bounds, rawData } = this - const chunk_size = bounds.getSize(new Vector3()).x // Math.round(Math.pow(chunkData.length, 1 / 3)) - - let written_blocks_count = 0 - - const level = MathUtils.clamp( - blockLocalPos.y + bufferOver.length, - bounds.min.y, - bounds.max.y, - ) - let buff_index = Math.max(level - blockLocalPos.y, 0) - let h = level - bounds.min.y // local height - // debug_mode && is_edge(local_pos.z, local_pos.x, h, patch_size - 2) - // ? BlockType.SAND - // : block_cache.type - let depth = 0 - while (h >= 0) { - const blocksIndex = - blockLocalPos.z * Math.pow(chunk_size, 2) + - h * chunk_size + - blockLocalPos.x - const blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type - const skip = - buff_index > 0 && - rawData[blocksIndex] !== undefined && - !bufferOver[buff_index] - if (!skip && blockType !== undefined) { - // #hack: disable block mode below ground to remove checkerboard excess - const skipBlockMode = - depth > 0 && - (bufferOver.length === 0 || bufferOver[buff_index] || buff_index < 0) - const blockMode = skipBlockMode ? BlockMode.DEFAULT : blockData.mode - rawData[blocksIndex] = ChunkFactory.defaultInstance.voxelDataEncoder( - blockType, - blockMode, - ) - blockType && written_blocks_count++ - } - h-- - buff_index-- - depth++ - } - return written_blocks_count + const buffIndex = this.getIndex(localPos) + this.rawData.set(buffer, buffIndex) } // abstract get chunkIds(): ChunkId[] diff --git a/src/datacontainers/OvergroundEntities.ts b/src/datacontainers/OvergroundEntities.ts index 2ebf283..e687996 100644 --- a/src/datacontainers/OvergroundEntities.ts +++ b/src/datacontainers/OvergroundEntities.ts @@ -1,16 +1,17 @@ import { Box2, Vector2, Vector3 } from "three"; import { asVect2, asVect3 } from "../common/utils"; +import { SchematicLoader } from "../tools/SchematicLoader"; import { ChunkContainer } from "./ChunkContainer"; import { PseudoDistributionMap } from "./RandomDistributionMap"; -export enum WorldObjectType { +export enum WorldItem { PineTree_10_5, AppleTree_10_5, SpruceTree_schem, } type WorldEntity = { - type: WorldObjectType + type: WorldItem template: ChunkContainer // builder template } @@ -31,28 +32,43 @@ export type InstancedEntity = { * - spawner (distribution) */ export class OvergroundEntities { - static registered: Record = {} + static registered: Record = {} // object external builder (proc generator, schematic loader) static registerEntity({ entity, spawner }: SpawnableEntity) { this.registered[entity.type] = { entity, spawner } } - static querySpawnedEntities(entityType: WorldObjectType, spawnRegion: Box2) { + static async loadSchematics(schematicFileUrls: Record, chunkDataEncoder?) { + const items = Object.entries(schematicFileUrls) + for await (const [id, fileUrl] of items) { + await SchematicLoader.createChunkContainer(fileUrl, chunkDataEncoder).then(template => { + const entity = { + type: parseInt(id), + template, + } + const spawner = new PseudoDistributionMap() + OvergroundEntities.registerEntity({ entity, spawner }) + }) + } + + } + + static querySpawnedEntities(entityType: WorldItem, spawnRegion: Box2) { const record = this.registered[entityType] const { entity, spawner } = record const entityDims = entity.template.bounds.getSize(new Vector3()) const entityOverlapTest = (testRegion: Box2, spawnLoc: Vector2) => new Box2().setFromCenterAndSize(spawnLoc, asVect2(entityDims)).intersectsBox(testRegion) - const spawnLocations = spawner.querySpawnLocations(spawnRegion, entityOverlapTest).map(loc => asVect3(loc, 0)) - const instancedEntities: InstancedEntity[] = spawnLocations.map(spawnLoc => ({ entity, spawnLoc })) - return instancedEntities + const spawnLocations = spawner.querySpawnLocations(spawnRegion, entityOverlapTest)//.map(loc => asVect3(loc, 0)) + // const instancedEntities: InstancedEntity[] = spawnLocations.map(spawnLoc => ({ entity, spawnLoc })) + return spawnLocations } // get object's buffer at queried location, from its instance static getInstancedEntityBuffer({ entity, spawnLoc }: InstancedEntity, queriedPos: Vector2) { // translate queried loc to template local pos const localPos = queriedPos.clone().sub(spawnLoc) - const buffer = entity.template.readBufferY(localPos) + const buffer = entity.template.readBuffer(localPos) return buffer } } diff --git a/src/index.ts b/src/index.ts index 0a8251f..f711dc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { Biome, BlockType } from './procgen/Biome' -export { WorldObjectType, OvergroundEntities } from './datacontainers/OvergroundEntities' +export { WorldItem, OvergroundEntities } from './datacontainers/OvergroundEntities' export { WorldConf } from './misc/WorldConfig' export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index 267e8d5..ba8580b 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -23,6 +23,9 @@ export class WorldConf { } static defaultDistMapPeriod = 4 * WorldConf.patchSize + static settings = { + useBiomeBilinearInterpolation: true + } static debug = { patch: { borderHighlightColor: BlockType.NONE, @@ -31,5 +34,8 @@ export class WorldConf { startPosHighlightColor: BlockType.NONE, splitSidesColoring: false, }, + schematics:{ + missingBlockType: BlockType.NONE + } } } diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index bf890dd..f72fbaf 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -95,7 +95,7 @@ export class Biome { seaLevel: 0, } - indexedConf = new Map + indexedConf = new Map constructor(biomeConf?: BiomeConfigs) { this.heatmap = new ProcLayer('heatmap') diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index de20c70..ae80159 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,15 +1,15 @@ -import { MathUtils, Vector2, Vector3 } from 'three' +import { Box3, MathUtils, Vector3 } from 'three' import { PatchId } from '../common/types' import { - asBox2, asVect2, asVect3, chunkBoxFromId, + parseThreeStub, serializeChunkId, } from '../common/utils' import { ChunkContainer } from '../datacontainers/ChunkContainer' -import { InstancedEntity } from '../datacontainers/OvergroundEntities' +import { OvergroundEntities, WorldItem } from '../datacontainers/OvergroundEntities' import { BlockMode, BlockType, GroundPatch, WorldConf } from '../index' // for debug use only @@ -53,18 +53,20 @@ export class ChunkFactory { } /** - * Assembles layers together: ground, world objects + * Assembles pieces together: ground, world objects */ - chunkifyPatch(groundPatchLayer: GroundPatch, overgroundEntities: InstancedEntity[]) { - const patchChunkIds = groundPatchLayer.id - ? ChunkFactory.default.genChunksIdsFromPatchId(groundPatchLayer.id) + chunkifyPatch(groundLayer: GroundPatch, overgroundItems: Record) { + const patchChunkIds = groundLayer.id + ? ChunkFactory.default.genChunksIdsFromPatchId(groundLayer.id) : [] const worldChunksStubs = patchChunkIds.map(chunkId => { const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) const worldChunk = new ChunkContainer(chunkBox) - // this.mergeGroundBlocks(worldChunk, patch) - // this.mergePatchEntities(worldChunk, patch, worldObjects) - this.mergePatchLayersToChunk(worldChunk, groundPatchLayer, overgroundEntities) + // this.mergePatchLayersToChunk(worldChunk, groundLayer, overgroundItems) + // merge items first so they don't override ground + this.mergeOvergroundItems(worldChunk, overgroundItems) + // merge ground layer after, overriding items blocks overlapping with ground + this.mergeGroundLayer(worldChunk, groundLayer) const worldChunkStub = { key: serializeChunkId(chunkId), data: worldChunk.rawData, @@ -74,24 +76,10 @@ export class ChunkFactory { return worldChunksStubs } - mergePatchLayersToChunk(chunkContainer: ChunkContainer, groundPatchLayer: GroundPatch, overgroundEntities: InstancedEntity[]) { - this.mergeGroundLayer(chunkContainer, groundPatchLayer) - this.mergePatchEntities(chunkContainer, groundPatchLayer, overgroundEntities) - } - - mergeLayersBuffers(){ - - } - - mergeOverlappingBuffers(){ - - } - - - mergeGroundLayer(worldChunk: ChunkContainer, patchGroundLayer: GroundPatch) { + mergeGroundLayer(worldChunk: ChunkContainer, groundLayer: GroundPatch) { const ymin = worldChunk.bounds.min.y const ymax = worldChunk.bounds.max.y - const blocks = patchGroundLayer.iterBlocksQuery(undefined, false) + const blocks = groundLayer.iterBlocksQuery(undefined, false) for (const block of blocks) { const blockLocalPos = block.localPos as Vector3 blockLocalPos.x += 1 @@ -100,53 +88,39 @@ export class ChunkFactory { const blockType = highlightPatchBorders(blockLocalPos, block.data.type) || block.data.type const blockMode = block.data.mode // generate ground buffer - let bufferCount = MathUtils.clamp(block.data.level - ymin, 0, ymax - ymin) - const groundBuffer = []; - while (bufferCount > 0) { - const rawData = this.voxelDataEncoder( + let buffSize = MathUtils.clamp(block.data.level - ymin, 0, ymax - ymin) + if (buffSize > 0) { + const groundBuffer = new Uint16Array(buffSize) + const bufferData = this.voxelDataEncoder( blockType, blockMode, ) - groundBuffer.push(rawData) - bufferCount-- + groundBuffer.fill(bufferData) + // worldChunk.writeSector() + const chunkBuffer = worldChunk.readBuffer(asVect2(blockLocalPos)) + chunkBuffer.set(groundBuffer) + worldChunk.writeBuffer(asVect2(blockLocalPos), chunkBuffer) } - - // worldChunk.writeSector() - worldChunk.writeBuffer(blockLocalPos, block.data, []) } } - - mergePatchEntities( - worldChunk: ChunkContainer, - groundLayer: GroundPatch, - overgroundEntities: InstancedEntity[], - ) { - overgroundEntities.forEach(instancedEntity => { - const { spawnLoc, entity } = instancedEntity - const entityBounds = asBox2(entity.template.bounds).translate(asVect2(spawnLoc)) - const center = entityBounds.getCenter(new Vector2()).floor() - spawnLoc.y = groundLayer.getBlock(center)?.pos.y || 0 - // return overlapping blocks between entity and container - const patchBlocksIter = groundLayer.iterBlocksQuery(entityBounds) - // iter over entity blocks - for (const block of patchBlocksIter) { - // const buffer = instancedEntity.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) - // translate queried loc to template local pos - const localPos = block.pos.clone().sub(spawnLoc) - let bufferData = entity.template.readBufferY(asVect2(localPos)) - const buffOffset = spawnLoc.y - block.pos.y - const buffSrc = Math.abs(Math.min(0, buffOffset)) - const buffDest = Math.max(buffOffset, 0) - bufferData = bufferData.copyWithin(buffDest, buffSrc) - bufferData = - buffOffset < 0 - ? bufferData.fill(BlockType.NONE, buffOffset) - : bufferData - block.localPos.x += 1 - block.localPos.z += 1 - worldChunk.writeBuffer(block.localPos, block.data, bufferData) - } + mergeOvergroundItems(worldChunk: ChunkContainer, overgroundItems: Record) { + Object.entries(overgroundItems).forEach(([type, spawnPlaces]) => { + const itemType = parseInt(type) as WorldItem + const { entity } = OvergroundEntities.registered[itemType] + spawnPlaces.forEach(spawnLoc => { + const dims = entity.template.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(spawnLoc, dims) + entityBounds.min.y = spawnLoc.y + entityBounds.max.y = spawnLoc.y + dims.y + entityBounds.min.floor() + entityBounds.max.floor() + const entityChunk = new ChunkContainer(entityBounds, 0) + entityChunk.rawData.set(entity.template.rawData) + ChunkContainer.copySourceToTarget(entityChunk, worldChunk) + }) }) } } diff --git a/src/tools/SchematicLoader.ts b/src/tools/SchematicLoader.ts index 5dddebf..54168c7 100644 --- a/src/tools/SchematicLoader.ts +++ b/src/tools/SchematicLoader.ts @@ -3,8 +3,10 @@ import Pako from "pako" import { Box3, Vector3 } from "three"; import { BlockType } from "../procgen/Biome"; import { ChunkContainer } from "../datacontainers/ChunkContainer"; +import { WorldConf } from "../misc/WorldConfig"; export class SchematicLoader { + static worldBlocksMapping: Record static async load(path: string) { // const schem = await Schematic.read(Buffer.from(schemData), '1.16.4') @@ -36,38 +38,30 @@ export class SchematicLoader { * @param schemBlocks * @returns */ - static async createChunkContainer(fileUrl: string) { - const toWorldBlockType = (rawType: string) => { - switch (rawType) { - case "air": - return BlockType.NONE - case "spruce_log": - return BlockType.TREE_TRUNK - case "spruce_leaves": - return BlockType.TREE_FOLIAGE - default: - console.log(rawType) - return BlockType.NONE - } - } + static async createChunkContainer(fileUrl: string, chunkDataEncoder = (val: BlockType) => val) { const rawData = await SchematicLoader.load(fileUrl) - const parsedData = await SchematicLoader.parse(rawData) - const schemBlocks = SchematicLoader.getBlocks(parsedData) + const parsedSchematic = await SchematicLoader.parse(rawData) + const schemBlocks = SchematicLoader.getBlocks(parsedSchematic) const dims = new Vector3(schemBlocks[0].length, schemBlocks.length, schemBlocks[0][0].length) - const orig = new Vector3(0, 140, 0) + const orig = new Vector3(0, 0, 0) const end = orig.clone().add(dims) const bbox = new Box3(orig, end) const chunkContainer = new ChunkContainer(bbox) - let index = 0 + 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] - const blockType = toWorldBlockType(rawType) + 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) - chunkContainer.rawData[blockIndex] = blockType + // const encodedData = ChunkFactory.defaultInstance.voxelDataEncoder(blockType || BlockType.NONE) + chunkContainer.rawData[blockIndex] = chunkDataEncoder(blockType || BlockType.NONE) //encodedData } } } From 285f317cb175d0841615625408bd56137799f218 Mon Sep 17 00:00:00 2001 From: etienne Date: Sat, 5 Oct 2024 13:00:34 +0000 Subject: [PATCH 05/13] refactor: items inventory + procedural items --- src/api/world-compute.ts | 19 ++-- src/datacontainers/OvergroundEntities.ts | 75 ---------------- src/index.ts | 7 +- src/misc/ItemsInventory.ts | 60 +++++++++++++ src/tools/ChunkFactory.ts | 42 ++++----- src/tools/ProceduralGenerators.ts | 106 +++++++++++------------ src/tools/SchematicLoader.ts | 1 - 7 files changed, 142 insertions(+), 168 deletions(-) delete mode 100644 src/datacontainers/OvergroundEntities.ts create mode 100644 src/misc/ItemsInventory.ts diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index fa308e1..d5f401c 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,16 +1,12 @@ import { Box2, Vector2, Vector3 } from 'three' - -import { EntityType, GroundPatch, WorldConf } from '../index' +import { GroundPatch, ItemsInventory, WorldConf } from '../index' import { Biome, BiomeInfluence, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { BiomeConfKey, Block, EntityData, NoiseLevelConf, PatchKey } from '../common/types' +import { Block, NoiseLevelConf, PatchKey } from '../common/types' import { asVect2, asVect3, bilinearInterpolation, getBoundsCornerPoints } from '../common/utils' -import { BlockConfigs, BlockData } from '../datacontainers/GroundPatch' -import { OvergroundEntities, WorldItem } from '../datacontainers/OvergroundEntities' -// import { BoardInputParams } from '../feats/BoardContainer' /** - * Brain of the world which can be run in separate worker + * Brain of the world runnable in separate worker */ /** @@ -34,9 +30,9 @@ export const computeBlocksBatch = ( const { spawnableItems } = blockData const queriedLoc = new Box2().setFromPoints([asVect2(blockPos)]) queriedLoc.max.addScalar(1) - false && includeEntitiesBlocks && spawnableItems.forEach(entityType => { + false && includeEntitiesBlocks && spawnableItems.forEach(itemType => { // multiple (overlapping) objects may be found at queried position - const [spawnedEntity] = OvergroundEntities.querySpawnedEntities(entityType, queriedLoc) + const [spawnedEntity] = ItemsInventory.querySpawnedEntities(itemType, queriedLoc) // const [foundEntity] = queryEntities(spawnRange).map(entityData => { // const { min, max } = entityData.bbox @@ -162,9 +158,8 @@ export const retrieveOvergroundItems = (bounds: Box2) => { }) const spawnedItems = {} Object.values(blockConfigs).forEach(blockConf => blockConf?.entities?.forEach(itemType => spawnedItems[itemType] = [])) - Object.keys(spawnedItems).forEach(type => { - const itemType = parseInt(type) as WorldItem - const spawnablePlaces = OvergroundEntities.querySpawnedEntities( + Object.keys(spawnedItems).forEach(itemType => { + const spawnablePlaces = ItemsInventory.querySpawnedEntities( itemType, bounds, ) diff --git a/src/datacontainers/OvergroundEntities.ts b/src/datacontainers/OvergroundEntities.ts deleted file mode 100644 index e687996..0000000 --- a/src/datacontainers/OvergroundEntities.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Box2, Vector2, Vector3 } from "three"; -import { asVect2, asVect3 } from "../common/utils"; -import { SchematicLoader } from "../tools/SchematicLoader"; -import { ChunkContainer } from "./ChunkContainer"; -import { PseudoDistributionMap } from "./RandomDistributionMap"; - -export enum WorldItem { - PineTree_10_5, - AppleTree_10_5, - SpruceTree_schem, -} - -type WorldEntity = { - type: WorldItem - template: ChunkContainer // builder template -} - -type SpawnableEntity = { - entity: WorldEntity - spawner: PseudoDistributionMap -} -// or SpawnedEntity -export type InstancedEntity = { - entity: WorldEntity, - spawnLoc: Vector3, -} - -/** - * Voxelizable items spawning inside world - * To register object type, should be provided object's - * - voxels definition (template) - * - spawner (distribution) - */ -export class OvergroundEntities { - static registered: Record = {} - // object external builder (proc generator, schematic loader) - - static registerEntity({ entity, spawner }: SpawnableEntity) { - this.registered[entity.type] = { entity, spawner } - } - - static async loadSchematics(schematicFileUrls: Record, chunkDataEncoder?) { - const items = Object.entries(schematicFileUrls) - for await (const [id, fileUrl] of items) { - await SchematicLoader.createChunkContainer(fileUrl, chunkDataEncoder).then(template => { - const entity = { - type: parseInt(id), - template, - } - const spawner = new PseudoDistributionMap() - OvergroundEntities.registerEntity({ entity, spawner }) - }) - } - - } - - static querySpawnedEntities(entityType: WorldItem, spawnRegion: Box2) { - const record = this.registered[entityType] - const { entity, spawner } = record - const entityDims = entity.template.bounds.getSize(new Vector3()) - const entityOverlapTest = (testRegion: Box2, spawnLoc: Vector2) => new Box2().setFromCenterAndSize(spawnLoc, asVect2(entityDims)).intersectsBox(testRegion) - const spawnLocations = spawner.querySpawnLocations(spawnRegion, entityOverlapTest)//.map(loc => asVect3(loc, 0)) - // const instancedEntities: InstancedEntity[] = spawnLocations.map(spawnLoc => ({ entity, spawnLoc })) - return spawnLocations - } - - // get object's buffer at queried location, from its instance - static getInstancedEntityBuffer({ entity, spawnLoc }: InstancedEntity, queriedPos: Vector2) { - // translate queried loc to template local pos - const localPos = queriedPos.clone().sub(spawnLoc) - const buffer = entity.template.readBuffer(localPos) - return buffer - } -} - diff --git a/src/index.ts b/src/index.ts index f711dc1..53c9adf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ + export { Biome, BlockType } from './procgen/Biome' -export { WorldItem, OvergroundEntities } from './datacontainers/OvergroundEntities' export { WorldConf } from './misc/WorldConfig' export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' @@ -13,8 +13,9 @@ export { GroundMap } from './datacontainers/GroundMap' 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 * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' -export * as ProceduralGenerators from './tools/ProceduralGenerators' +// export * as ProceduralGenerators from './tools/ProceduralGenerators' diff --git a/src/misc/ItemsInventory.ts b/src/misc/ItemsInventory.ts new file mode 100644 index 0000000..4845f15 --- /dev/null +++ b/src/misc/ItemsInventory.ts @@ -0,0 +1,60 @@ +import { Box2, Vector2, Vector3 } from "three" +import { asVect2 } from "../common/utils" +import { ChunkContainer } from "../datacontainers/ChunkContainer" +import { PseudoDistributionMap } from "../datacontainers/RandomDistributionMap" +import { ProceduralItemGenerator, ProcItemConf } from "../tools/ProceduralGenerators" +import { SchematicLoader } from "../tools/SchematicLoader" + +export type ItemId = string + +/** + * Referencing all items generated from procedural or external schematic templates + */ + +export class ItemsInventory { + static catalog: Record = {} + static spawners: Record = {} + /** + * Populate from schematics + * @param schematicFileUrls + * @param optionalDataEncoder + */ + static async importSchematics(schematicFileUrls: Record, optionalDataEncoder?: () => number) { + const items = Object.entries(schematicFileUrls) + for await (const [id, fileUrl] of items) { + const chunkDef = await SchematicLoader.createChunkContainer(fileUrl, optionalDataEncoder) + // const spawner = new PseudoDistributionMap() + ItemsInventory.catalog[id] = chunkDef + } + } + + static async importProceduralObjects(procItemsBatch: Record, optionalDataEncoder?: () => number) { + const items = Object.entries(procItemsBatch) + for await (const [id, conf] of items) { + const itemChunk = ProceduralItemGenerator.voxelizeItem(conf.category, conf.params, optionalDataEncoder) + // const spawner = new PseudoDistributionMap() + if (itemChunk) { + ItemsInventory.catalog[id] = itemChunk + } + } + } + + static querySpawnedEntities(itemId: string, spawnRegion: Box2) { + const itemChunk = this.catalog[itemId] + const itemSpawner = this.spawners[itemId] + + let spawnPlaces: Vector2[] = [] + if (itemChunk && itemSpawner) { + const dims = itemChunk.bounds.getSize(new Vector3()) + const entityOverlapTest = (testRegion: Box2, spawnLoc: Vector2) => new Box2().setFromCenterAndSize(spawnLoc, asVect2(dims)).intersectsBox(testRegion) + spawnPlaces = itemSpawner.querySpawnLocations(spawnRegion, entityOverlapTest)//.map(loc => asVect3(loc, 0)) + // const instancedEntities: InstancedEntity[] = spawnLocations.map(spawnLoc => ({ entity, spawnLoc })) + } + + return spawnPlaces + } +} + +export class ItemsSpawner { + static spawners: Record +} \ No newline at end of file diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index ae80159..729cca9 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -5,12 +5,11 @@ import { asVect2, asVect3, chunkBoxFromId, - parseThreeStub, serializeChunkId, } from '../common/utils' import { ChunkContainer } from '../datacontainers/ChunkContainer' -import { OvergroundEntities, WorldItem } from '../datacontainers/OvergroundEntities' -import { BlockMode, BlockType, GroundPatch, WorldConf } from '../index' +import { BlockMode, BlockType, GroundPatch, ItemsInventory, WorldConf } from '../index' +import { ItemId } from '../misc/ItemsInventory' // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -55,7 +54,7 @@ export class ChunkFactory { /** * Assembles pieces together: ground, world objects */ - chunkifyPatch(groundLayer: GroundPatch, overgroundItems: Record) { + chunkifyPatch(groundLayer: GroundPatch, overgroundItems: Record) { const patchChunkIds = groundLayer.id ? ChunkFactory.default.genChunksIdsFromPatchId(groundLayer.id) : [] @@ -104,23 +103,24 @@ export class ChunkFactory { } } - mergeOvergroundItems(worldChunk: ChunkContainer, overgroundItems: Record) { - Object.entries(overgroundItems).forEach(([type, spawnPlaces]) => { - const itemType = parseInt(type) as WorldItem - const { entity } = OvergroundEntities.registered[itemType] - spawnPlaces.forEach(spawnLoc => { - const dims = entity.template.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(spawnLoc, dims) - entityBounds.min.y = spawnLoc.y - entityBounds.max.y = spawnLoc.y + dims.y - entityBounds.min.floor() - entityBounds.max.floor() - const entityChunk = new ChunkContainer(entityBounds, 0) - entityChunk.rawData.set(entity.template.rawData) - ChunkContainer.copySourceToTarget(entityChunk, worldChunk) - }) + mergeOvergroundItems(worldChunk: ChunkContainer, overgroundItems: Record) { + Object.entries(overgroundItems).forEach(([itemType, spawnPlaces]) => { + const itemChunk = ItemsInventory.catalog[itemType] + if (itemChunk) { + spawnPlaces.forEach(spawnLoc => { + const dims = itemChunk.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(spawnLoc, dims) + entityBounds.min.y = spawnLoc.y + entityBounds.max.y = spawnLoc.y + dims.y + entityBounds.min.floor() + entityBounds.max.floor() + const entityChunk = new ChunkContainer(entityBounds, 0) + entityChunk.rawData.set(itemChunk.rawData) + ChunkContainer.copySourceToTarget(entityChunk, worldChunk) + }) + } }) } } diff --git a/src/tools/ProceduralGenerators.ts b/src/tools/ProceduralGenerators.ts index f4edb3e..421a408 100644 --- a/src/tools/ProceduralGenerators.ts +++ b/src/tools/ProceduralGenerators.ts @@ -1,39 +1,58 @@ import { Vector3, Vector2, Box3 } from 'three' import { asVect2 } from '../common/utils' import { ChunkContainer } from '../datacontainers/ChunkContainer' -import { BlockType, PseudoDistributionMap } from '../index' +import { BlockType } from '../index' -export type TreeDef = { - // type: TreeKind, - size: number, - radius: number +export enum ProcItemCategory { + Tree, + Boulder, + Grass } -export abstract class ProceduralTree { - // type - size - radius +export enum ProcItemType { + AppleTree, + PineTree +} - constructor(size: number, radius: number) { - // super(new Box3(new Vector3(), new Vector3(2 * radius, size, 2 * radius))) - this.size = size - this.radius = radius - // this.type = type - } +export type ProcItemConf = { + category: ProcItemCategory, + params: any +} - abstract generate(xzProj: number, y: number, range: number): BlockType; +type TreeGenerator = (xzProj: number, y: number, range: number) => BlockType - // get key() { - // const { size, radius, type } = this - // const treeDef = { - // size, radius, type - // } - // return treeKey(treeDef) - // } +const AppleTreeGen = (xzProj: number, y: number, range: number): BlockType => { + const dist = Math.sqrt(Math.pow(xzProj, 2) + Math.pow(y, 2)) + const isFoliage = dist <= range + return isFoliage ? BlockType.TREE_FOLIAGE : BlockType.NONE +} + +const PineTreeGen = (xzProj: number, y: number, range: number): BlockType => { + const dist = xzProj // xzProj*(y+radius) + const isFoliage = dist <= range * (1 - (0.35 * (y + range)) / range) + return isFoliage ? BlockType.TREE_FOLIAGE_2 : BlockType.NONE +} - voxelize() { - const { size: treeSize, radius: treeRadius } = this - const treeBounds = new Box3(new Vector3(), new Vector3(2 * treeRadius, treeSize, 2 * treeRadius)) +type ProceduralGenerator = TreeGenerator + +const ProceduralGenerators: Record = { + [ProcItemType.AppleTree]: AppleTreeGen, + [ProcItemType.PineTree]: PineTreeGen +} + +export class ProceduralItemGenerator { + + static voxelizeItem(itemCat: ProcItemCategory, itemParams: any, optionalDataEncoder?: () => number) { + switch (itemCat) { + case ProcItemCategory.Tree: + const { treeType, treeSize, treeRadius } = itemParams + return this.voxelizeTree(treeType, treeSize, treeRadius, optionalDataEncoder) + } + } + + static voxelizeTree(treeType: ProcItemType, treeSize: number, treeRadius: number, dataEncoder = (blockType: BlockType) => blockType) { + const treeGenerator = ProceduralGenerators[treeType] + 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()) const { min, max } = treeBounds @@ -45,47 +64,22 @@ export abstract class ProceduralTree { if (xzProj.length() > 0) { if (y < min.y + treeSize) { // empty space around trunk between ground and trunk top - treeChunk.rawData[index++] = BlockType.NONE + treeChunk.rawData[index++] = dataEncoder(BlockType.NONE) } else { // tree foliage - const blockType = this.generate( + const blockType = treeGenerator( xzProj.length(), y - (min.y + treeSize + treeRadius), treeRadius, ) - treeChunk.rawData[index++] = blockType + treeChunk.rawData[index++] = dataEncoder(blockType) } } else { // tree trunk - treeChunk.rawData[index++] = BlockType.TREE_TRUNK + treeChunk.rawData[index++] = dataEncoder(BlockType.TREE_TRUNK) } } + return treeChunk } -} - -export class AppleTree extends ProceduralTree { - distribution: PseudoDistributionMap - constructor(size: number, radius: number) { - super(size, radius) - this.distribution = new PseudoDistributionMap() - } - generate(xzProj: number, y: number, range: number): BlockType { - const dist = Math.sqrt(Math.pow(xzProj, 2) + Math.pow(y, 2)) - const isFoliage = dist <= range - return isFoliage ? BlockType.TREE_FOLIAGE : BlockType.NONE - } - -} -export class PineTree extends ProceduralTree { - distribution: PseudoDistributionMap - constructor(size: number, radius: number) { - super(size, radius) - this.distribution = new PseudoDistributionMap() - } - generate(xzProj: number, y: number, range: number): BlockType { - const dist = xzProj // xzProj*(y+radius) - const isFoliage = dist <= range * (1 - (0.35 * (y + range)) / range) - return isFoliage ? BlockType.TREE_FOLIAGE_2 : BlockType.NONE - } } \ No newline at end of file diff --git a/src/tools/SchematicLoader.ts b/src/tools/SchematicLoader.ts index 54168c7..5ba7002 100644 --- a/src/tools/SchematicLoader.ts +++ b/src/tools/SchematicLoader.ts @@ -68,7 +68,6 @@ export class SchematicLoader { return chunkContainer } - static getBlocks(schemData) { // Get dimensions of the schematic const width = schemData.value.Width.value; From 7283300d4fbf1208e9cd7ac04ea331c45e7a14c4 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 11 Oct 2024 16:37:19 +0000 Subject: [PATCH 06/13] feat: on demand async schematics loading + remove spawn logic from inventory --- src/misc/ItemsInventory.ts | 61 ++++++++++------------- src/third-party/{nbt.ts => nbt_custom.ts} | 21 ++++---- src/tools/ChunkFactory.ts | 31 ++++++------ src/tools/ProceduralGenerators.ts | 14 +++--- src/tools/SchematicLoader.ts | 7 ++- 5 files changed, 67 insertions(+), 67 deletions(-) rename src/third-party/{nbt.ts => nbt_custom.ts} (95%) diff --git a/src/misc/ItemsInventory.ts b/src/misc/ItemsInventory.ts index 4845f15..d173572 100644 --- a/src/misc/ItemsInventory.ts +++ b/src/misc/ItemsInventory.ts @@ -1,60 +1,53 @@ -import { Box2, Vector2, Vector3 } from "three" -import { asVect2 } from "../common/utils" import { ChunkContainer } from "../datacontainers/ChunkContainer" -import { PseudoDistributionMap } from "../datacontainers/RandomDistributionMap" import { ProceduralItemGenerator, ProcItemConf } from "../tools/ProceduralGenerators" import { SchematicLoader } from "../tools/SchematicLoader" -export type ItemId = string +export type ItemType = string /** * Referencing all items generated from procedural or external schematic templates */ export class ItemsInventory { - static catalog: Record = {} - static spawners: Record = {} + static externalResources: { + procItemsConfigs: Record + schemFileUrls: Record + } = { + procItemsConfigs: {}, + schemFileUrls: {} + } + static catalog: Record = {} + // static spawners: Record = {} /** * Populate from schematics * @param schematicFileUrls * @param optionalDataEncoder */ - static async importSchematics(schematicFileUrls: Record, optionalDataEncoder?: () => number) { - const items = Object.entries(schematicFileUrls) - for await (const [id, fileUrl] of items) { - const chunkDef = await SchematicLoader.createChunkContainer(fileUrl, 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] = chunkDef + ItemsInventory.catalog[id] = chunk } + return chunk } - static async importProceduralObjects(procItemsBatch: Record, optionalDataEncoder?: () => number) { - const items = Object.entries(procItemsBatch) - for await (const [id, conf] of items) { - const itemChunk = ProceduralItemGenerator.voxelizeItem(conf.category, conf.params, optionalDataEncoder) + 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 (itemChunk) { - ItemsInventory.catalog[id] = itemChunk + if (chunk) { + ItemsInventory.catalog[id] = chunk } } + return chunk } - static querySpawnedEntities(itemId: string, spawnRegion: Box2) { - const itemChunk = this.catalog[itemId] - const itemSpawner = this.spawners[itemId] - - let spawnPlaces: Vector2[] = [] - if (itemChunk && itemSpawner) { - const dims = itemChunk.bounds.getSize(new Vector3()) - const entityOverlapTest = (testRegion: Box2, spawnLoc: Vector2) => new Box2().setFromCenterAndSize(spawnLoc, asVect2(dims)).intersectsBox(testRegion) - spawnPlaces = itemSpawner.querySpawnLocations(spawnRegion, entityOverlapTest)//.map(loc => asVect3(loc, 0)) - // const instancedEntities: InstancedEntity[] = spawnLocations.map(spawnLoc => ({ entity, spawnLoc })) - } - - return spawnPlaces + static async getItem(itemId: string) { + return this.catalog[itemId] || await this.importSchematic(itemId) || this.importProcItem(itemId) } -} - -export class ItemsSpawner { - static spawners: Record } \ No newline at end of file diff --git a/src/third-party/nbt.ts b/src/third-party/nbt_custom.ts similarity index 95% rename from src/third-party/nbt.ts rename to src/third-party/nbt_custom.ts index 84b865e..4d3d3b8 100644 --- a/src/third-party/nbt.ts +++ b/src/third-party/nbt_custom.ts @@ -1,20 +1,19 @@ /** - * This is code from + * Customized and refactored code originating from * * NBT.js - a JavaScript parser for NBT archives * by Sijmen Mulder * - * refactored to be more ES6 friendly */ // var zlib = typeof require !== 'undefined' ? require('zlib') : window.zlib; -function hasGzipHeader(data) { +function hasGzipHeader(data: any) { var head = new Uint8Array(data.slice(0, 2)); return head.length === 2 && head[0] === 0x1f && head[1] === 0x8b; } -function decodeUTF8(array: []) { +function decodeUTF8(array: any[]) { var codepoints = [], i; for (i = 0; i < array.length; i++) { if ((array[i] & 0x80) === 0) { @@ -48,9 +47,7 @@ function decodeUTF8(array: []) { return String.fromCharCode.apply(null, codepoints); } -/* Not all environments, in particular PhantomJS, supply - Uint8Array.slice() */ -function sliceUint8Array(array, begin, end) { +function sliceUint8Array(array: Uint8Array, begin: number, end: number) { if ('slice' in array) { return array.slice(begin, end); } else { @@ -226,7 +223,7 @@ export class NBTReader { * // -> { name: 'My Level', * // value: { foo: { type: int, value: 42 }, * // bar: { type: string, value: 'Hi!' }}} */ - static parseUncompressed(data) { + static parseUncompressed(data: Iterable) { if (!data) { throw new Error('Argument "data" is falsy'); } var reader = new NBTReader(data) @@ -287,9 +284,11 @@ export class NBTReader { var buffer; if (data.length) { buffer = data; - } else if (typeof Buffer !== 'undefined') { - buffer = new Buffer(data); - } else { + } + // else if (typeof Buffer !== 'undefined') { + // buffer = new Buffer(data); + // } + else { /* In the browser? Unknown zlib library. Let's settle for Uint8Array and see what happens. */ buffer = new Uint8Array(data); diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 729cca9..6c35d63 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -9,7 +9,7 @@ import { } from '../common/utils' import { ChunkContainer } from '../datacontainers/ChunkContainer' import { BlockMode, BlockType, GroundPatch, ItemsInventory, WorldConf } from '../index' -import { ItemId } from '../misc/ItemsInventory' +import { ItemType } from '../misc/ItemsInventory' // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -21,9 +21,9 @@ const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { export class ChunkFactory { // eslint-disable-next-line no-use-before-define - static defaultInstance: ChunkFactory + static instance: ChunkFactory // eslint-disable-next-line @typescript-eslint/no-unused-vars - voxelDataEncoder = (blockType: BlockType, _blockMode?: BlockMode) => + chunkDataEncoder = (blockType: BlockType, _blockMode?: BlockMode) => blockType || BlockType.NONE chunksRange = { @@ -32,8 +32,8 @@ export class ChunkFactory { } static get default() { - this.defaultInstance = this.defaultInstance || new ChunkFactory() - return this.defaultInstance + this.instance = this.instance || new ChunkFactory() + return this.instance } setChunksGenRange(ymin: number, ymax: number) { @@ -54,16 +54,17 @@ export class ChunkFactory { /** * Assembles pieces together: ground, world objects */ - chunkifyPatch(groundLayer: GroundPatch, overgroundItems: Record) { + async chunkifyPatch(groundLayer: GroundPatch, overgroundItems: Record) { const patchChunkIds = groundLayer.id ? ChunkFactory.default.genChunksIdsFromPatchId(groundLayer.id) : [] - const worldChunksStubs = patchChunkIds.map(chunkId => { + // const worldChunksStubs = patchChunkIds.map(async chunkId => { + const worldChunksStubs = await Promise.all(patchChunkIds.map(async chunkId => { const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) const worldChunk = new ChunkContainer(chunkBox) // this.mergePatchLayersToChunk(worldChunk, groundLayer, overgroundItems) // merge items first so they don't override ground - this.mergeOvergroundItems(worldChunk, overgroundItems) + await this.mergeOvergroundItems(worldChunk, overgroundItems) // merge ground layer after, overriding items blocks overlapping with ground this.mergeGroundLayer(worldChunk, groundLayer) const worldChunkStub = { @@ -71,7 +72,7 @@ export class ChunkFactory { data: worldChunk.rawData, } return worldChunkStub - }) + })) return worldChunksStubs } @@ -90,7 +91,7 @@ export class ChunkFactory { let buffSize = MathUtils.clamp(block.data.level - ymin, 0, ymax - ymin) if (buffSize > 0) { const groundBuffer = new Uint16Array(buffSize) - const bufferData = this.voxelDataEncoder( + const bufferData = this.chunkDataEncoder( blockType, blockMode, ) @@ -103,9 +104,11 @@ export class ChunkFactory { } } - mergeOvergroundItems(worldChunk: ChunkContainer, overgroundItems: Record) { - Object.entries(overgroundItems).forEach(([itemType, spawnPlaces]) => { - const itemChunk = ItemsInventory.catalog[itemType] + async mergeOvergroundItems(worldChunk: ChunkContainer, overgroundItems: Record) { + // Object.entries(overgroundItems).forEach(([itemType, spawnPlaces]) => { + const entries = Object.entries(overgroundItems) + for await (const [itemType, spawnPlaces] of entries) { + const itemChunk = await ItemsInventory.getItem(itemType) if (itemChunk) { spawnPlaces.forEach(spawnLoc => { const dims = itemChunk.bounds.getSize(new Vector3()) @@ -121,6 +124,6 @@ export class ChunkFactory { ChunkContainer.copySourceToTarget(entityChunk, worldChunk) }) } - }) + }//) } } diff --git a/src/tools/ProceduralGenerators.ts b/src/tools/ProceduralGenerators.ts index 421a408..0ec66e9 100644 --- a/src/tools/ProceduralGenerators.ts +++ b/src/tools/ProceduralGenerators.ts @@ -41,16 +41,18 @@ const ProceduralGenerators: Record = { } export class ProceduralItemGenerator { + static chunkDataEncoder = (blockType: BlockType) => blockType - static voxelizeItem(itemCat: ProcItemCategory, itemParams: any, optionalDataEncoder?: () => number) { + static voxelizeItem(itemCat: ProcItemCategory, itemParams: any) { switch (itemCat) { case ProcItemCategory.Tree: const { treeType, treeSize, treeRadius } = itemParams - return this.voxelizeTree(treeType, treeSize, treeRadius, optionalDataEncoder) + return this.voxelizeTree(treeType, treeSize, treeRadius) } } - static voxelizeTree(treeType: ProcItemType, treeSize: number, treeRadius: number, dataEncoder = (blockType: BlockType) => blockType) { + 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 treeChunk = new ChunkContainer(treeBounds) @@ -64,7 +66,7 @@ export class ProceduralItemGenerator { if (xzProj.length() > 0) { if (y < min.y + treeSize) { // empty space around trunk between ground and trunk top - treeChunk.rawData[index++] = dataEncoder(BlockType.NONE) + treeChunk.rawData[index++] = chunkDataEncoder(BlockType.NONE) } else { // tree foliage const blockType = treeGenerator( @@ -72,11 +74,11 @@ export class ProceduralItemGenerator { y - (min.y + treeSize + treeRadius), treeRadius, ) - treeChunk.rawData[index++] = dataEncoder(blockType) + treeChunk.rawData[index++] = chunkDataEncoder(blockType) } } else { // tree trunk - treeChunk.rawData[index++] = dataEncoder(BlockType.TREE_TRUNK) + treeChunk.rawData[index++] = chunkDataEncoder(BlockType.TREE_TRUNK) } } return treeChunk diff --git a/src/tools/SchematicLoader.ts b/src/tools/SchematicLoader.ts index 5ba7002..2d71504 100644 --- a/src/tools/SchematicLoader.ts +++ b/src/tools/SchematicLoader.ts @@ -1,4 +1,4 @@ -import { NBTReader } from "../third-party/nbt"; +import { NBTReader } from "../third-party/nbt_custom"; import Pako from "pako" import { Box3, Vector3 } from "three"; import { BlockType } from "../procgen/Biome"; @@ -7,6 +7,7 @@ import { WorldConf } from "../misc/WorldConfig"; export class SchematicLoader { static worldBlocksMapping: Record + static chunkDataEncoder = (blockType: BlockType) => blockType static async load(path: string) { // const schem = await Schematic.read(Buffer.from(schemData), '1.16.4') @@ -38,7 +39,9 @@ export class SchematicLoader { * @param schemBlocks * @returns */ - static async createChunkContainer(fileUrl: string, chunkDataEncoder = (val: BlockType) => val) { + static async createChunkContainer(fileUrl: string) { + const { chunkDataEncoder } = SchematicLoader + const rawData = await SchematicLoader.load(fileUrl) const parsedSchematic = await SchematicLoader.parse(rawData) const schemBlocks = SchematicLoader.getBlocks(parsedSchematic) From 2ddf071772dc3be8718a8e25e80bef0d1aa5513e Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 11 Oct 2024 17:10:42 +0000 Subject: [PATCH 07/13] refactor: biome bilinear interpolation (smoothstep), world-compute biome influences calculations, misc types renaming, --- src/api/world-compute.ts | 113 +++++++++++++------- src/common/types.ts | 32 +++++- src/common/utils.ts | 147 +++++++++++++------------- src/procgen/Biome.ts | 217 ++++++++++++++++++++++----------------- 4 files changed, 302 insertions(+), 207 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index d5f401c..84e8d9b 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,16 +1,19 @@ import { Box2, Vector2, Vector3 } from 'three' -import { GroundPatch, ItemsInventory, WorldConf } from '../index' -import { Biome, BiomeInfluence, BlockType } from '../procgen/Biome' +import { GroundPatch, ItemsInventory, PseudoDistributionMap, WorldConf } from '../index' +import { Biome, BiomeInfluence, BiomeType, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { Block, NoiseLevelConf, PatchKey } from '../common/types' -import { asVect2, asVect3, bilinearInterpolation, getBoundsCornerPoints } from '../common/utils' +import { Block, NoiseLevelConf, PatchBoundId, PatchKey } from '../common/types' +import { asVect2, asVect3, bilinearInterpolation, getPatchBoundingPoints, getPatchId, serializePatchId } from '../common/utils' +import { ItemType } from '../misc/ItemsInventory' -/** - * Brain of the world runnable in separate worker - */ +type PatchBoundingBiomes = Record /** - * Individual blocks requests + * Brain of the world runnable in separate worker + * Support for: + * - individual blocks request (batch) + * - ground layer request (patch) + * - overground items (patch) */ /** @@ -20,10 +23,22 @@ import { asVect2, asVect3, bilinearInterpolation, getBoundsCornerPoints } from ' * @returns */ export const computeBlocksBatch = ( - blockPosBatch: Vector3[], + blockPosBatch: Vector2[], params = { includeEntitiesBlocks: false }, ) => { const { includeEntitiesBlocks } = params + // sort blocks by patch + const blocksByPatch: Record = { + + } + blockPosBatch.forEach(pos => { + const patchKey = serializePatchId(getPatchId(pos, WorldConf.regularPatchDimensions)) + blocksByPatch[patchKey] = blocksByPatch[patchKey] || [] + blocksByPatch[patchKey]?.push(pos) + }) + Object.entries(blocksByPatch).forEach(([patchKey, patchBlocks]) => { + + }) const blocksBatch = blockPosBatch.map(({ x, z }) => { const blockPos = new Vector3(x, 0, z) const blockData = computeGroundBlock(blockPos) @@ -31,7 +46,7 @@ export const computeBlocksBatch = ( const queriedLoc = new Box2().setFromPoints([asVect2(blockPos)]) queriedLoc.max.addScalar(1) false && includeEntitiesBlocks && spawnableItems.forEach(itemType => { - // multiple (overlapping) objects may be found at queried position + // several (overlapping) objects may be found at queried position const [spawnedEntity] = ItemsInventory.querySpawnedEntities(itemType, queriedLoc) // const [foundEntity] = queryEntities(spawnRange).map(entityData => { @@ -68,7 +83,7 @@ export const computeGroundBlock = (blockPos: Vector3, biomeInfluence?: BiomeInfl const prevLevelConf = noiseLevel.prev?.data const nextLevelConf = noiseLevel.next?.data const confKey = currLevelConf.key - const confIndex = Biome.instance.getConfIndex(currLevelConf.key) + // const confIndex = Biome.instance.getConfIndex(currLevelConf.key) // const confData = Biome.instance.indexedConf.get(confIndex) const level = Heightmap.instance.getGroundLevel( blockPos, @@ -112,21 +127,38 @@ export const bakePatch = (boundsOrPatchKey: PatchKey | Box2) => { return groundLayer } -// Patch ground layer -export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { - const groundPatch = new GroundPatch(boundsOrPatchKey) +const getBiomeBoundsInfluences = (bounds: Box2) => { + const { xMyM, xMyP, xPyM, xPyP } = PatchBoundId // eval biome at patch corners - const equals = (v1, v2) => { - const different = Object.keys(v1).find(k => v1[k] !== v2[k]) + const equals = (v1: BiomeInfluence, v2: BiomeInfluence) => { + const different = Object.keys(v1).find(k => v1[k as BiomeType] !== v2[k as BiomeType]) return !different } - const [p11, p12, p21, p22] = getBoundsCornerPoints(groundPatch.bounds) - const [v11, v12, v21, v22] = [p11, p12, p21, p22].map(pos => { - const biomeInfluence = Biome.instance.getBiomeInfluence(pos) + const boundsPoints = getPatchBoundingPoints(bounds) + const boundsInfluences = {} as PatchBoundingBiomes + [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(v11, v12) && equals(v11, v21) && equals(v11, v22) + 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) + } +} + +// Patch ground layer +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 = { @@ -136,9 +168,9 @@ export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { let blockIndex = 0 for (const block of blocks) { // EXPERIMENTAL: is it faster to perform bilinear interpolation rather than sampling biome for each block? - const getBlockBiome = () => WorldConf.settings.useBiomeBilinearInterpolation && bilinearInterpolation(asVect2(block.pos), groundPatch.bounds, { v11, v12, v21, v22 }) + // if biome is the same at each patch corners, no need to interpolate - const blockData = computeGroundBlock(block.pos, allEquals ? v11 : getBlockBiome()) + const blockData = computeGroundBlock(block.pos, getBlockBiome(asVect2(block.pos), groundPatch.bounds, biomeBoundsInfluences)) level.min = Math.min(min.y, blockData.level) level.max = Math.max(max.y, blockData.level) groundPatch.writeBlockData(blockIndex, blockData) @@ -148,29 +180,32 @@ export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { return groundPatch } -export const retrieveOvergroundItems = (bounds: Box2) => { +export const retrieveOvergroundItems = async (bounds: Box2) => { // spawnable items based on soil type found in this specific region const blockConfigs: any = {} - const [p11, p12, p21, p22] = getBoundsCornerPoints(bounds); - [p11, p12, p21, p22].forEach(pos => { + const patchBoundingPoints = getPatchBoundingPoints(bounds); + // eval configs at each patch corners + Object.values(patchBoundingPoints).forEach(pos => { const block = computeGroundBlock(asVect3(pos)) blockConfigs[block.confKey] = Biome.instance.indexedConf.get(block.confKey)?.data }) const spawnedItems = {} - Object.values(blockConfigs).forEach(blockConf => blockConf?.entities?.forEach(itemType => spawnedItems[itemType] = [])) - Object.keys(spawnedItems).forEach(itemType => { - const spawnablePlaces = ItemsInventory.querySpawnedEntities( - itemType, - bounds, - ) - spawnablePlaces.forEach(itemPos => { - // confirm entities and add spawn elevation - const block = computeGroundBlock(asVect3(itemPos)) - const blockConf = Biome.instance.indexedConf.get(block.confKey)?.data - if (blockConf?.entities?.find(val => val === itemType)) - spawnedItems[itemType].push(asVect3(itemPos, block.level)) - }) - }) + // Object.values(blockConfigs).forEach(blockConf => blockConf?.entities?.forEach(itemType => spawnedItems[itemType] = [])) + // Object.keys(spawnedItems).forEach(itemType => { + // const spawnablePlaces = ItemsInventory.querySpawnedEntities( + // itemType, + // bounds, + // ) + // spawnablePlaces.forEach(itemPos => { + // // confirm entities and add spawn elevation + // const blockConf = Biome.instance.indexedConf.get(itemBlock.confKey)?.data + // if (blockConf?.flora)//find(val => val === itemType)) + // { + // confirmedItems[itemType] = confirmedItems[itemType] || []; + // (confirmedItems[itemType] as Vector3[]).push(asVect3(itemPos, itemBlock.level)) + // } + // }) + // }) return spawnedItems } diff --git a/src/common/types.ts b/src/common/types.ts index b1e2fa7..d71f7ee 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -34,7 +34,35 @@ export type AllCardinalDirections = | CardinalDirections | IntercardinalDirections -export enum Adjacent2dPos { +// export enum SurfaceBounds { +// R_DOWN, // xM,yM +// R_UP, // xM,yP +// L_UP, // xP,yP +// L_DOWN // xP,yM +// } + +export enum PatchBoundId { + xMyM="xMyM", + xMyP="xMyP", + xPyP="xPyP", + xPyM="xPyM", +} + +export type PatchBoundingPoints = Record + + +export enum ChunkBoundId { + xMyMzM, + xMyPzM, + xPyPzM, + xPyMzM, + xMyMzP, + xMyPzP, + xPyPzP, + xPyMzP, +} + +export enum SurfaceNeighbour { center, left, right, @@ -46,7 +74,7 @@ export enum Adjacent2dPos { bottomright, } -export enum Adjacent3dPos { +export enum VolumeNeighbour { xMyMzM, xMyMz0, xMyMzP, diff --git a/src/common/utils.ts b/src/common/utils.ts index 53276cf..5745e5e 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,14 +1,16 @@ import { Box2, Box3, Vector2, Vector2Like, Vector3, Vector3Like } from 'three' import { - Adjacent2dPos, - Adjacent3dPos, + SurfaceNeighbour, + VolumeNeighbour, ChunkId, ChunkKey, - MetadataFields, + BiomeTerrainConfigFields, NoiseLevelConf, PatchId, PatchKey, + PatchBoundId, + PatchBoundingPoints, } from './types' // Clamp number between two values: @@ -31,8 +33,13 @@ const vectRoundToDec = (input: Vector2 | Vector3, n_pow: number) => { return output } +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 MappingRangeFinder = (item: LinkedList, inputVal: number) => item.next && inputVal > (item.next.data as MappingData).x -export const MappingRangeSorter = (item1: MetadataFields, item2: MetadataFields) => +export const MappingRangeSorter = (item1: BiomeTerrainConfigFields, item2: BiomeTerrainConfigFields) => item1.x - item2.x /** @@ -49,13 +56,14 @@ const findMatchingRange = (inputVal: number, noiseMappings: NoiseLevelConf) => { } /** - * | | - * y2 --+-------+-- - * | + P | - * | | - * y1 --+-------+-- - * | | - * x1 x2 + * + * y2 - p12-----p22 + * | + p | + * | | + * y1 - p11-----p21 + * | | + * x1 x2 + * * @param p * @param p11 * @param p12 @@ -63,7 +71,7 @@ const findMatchingRange = (inputVal: number, noiseMappings: NoiseLevelConf) => { * @param p21 * @returns */ -const bilinearInterpolation = (p: Vector2, bounds: Box2, { v11, v12, v21, v22 }: 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 @@ -78,7 +86,7 @@ const bilinearInterpolation = (p: Vector2, bounds: Box2, { v11, v12, v21, v22 }: Object.keys(first).forEach(k => res[k] = sumComponents(k, items)) return res } - const mul = (w: number, v: any) => { + const mul = (w: number, v: T) => { const res = { ...v } Object.keys(res).forEach(k => res[k] *= w) return res @@ -88,10 +96,10 @@ const bilinearInterpolation = (p: Vector2, bounds: Box2, { v11, v12, v21, v22 }: 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, v11) - const m12 = mul(w12, v12) - const m21 = mul(w21, v21) - const m22 = mul(w22, v22) + 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 res = add(m11, m12, m21, m22) return res } @@ -124,10 +132,10 @@ const invDistWeighting = (cornerPointsValues: [p: Vector2, v: any][], point: Vec * - LEFT/RIGHT */ const directNeighbours2D = [ - Adjacent2dPos.left, - Adjacent2dPos.right, - Adjacent2dPos.top, - Adjacent2dPos.bottom, + SurfaceNeighbour.left, + SurfaceNeighbour.right, + SurfaceNeighbour.top, + SurfaceNeighbour.bottom, ] /** @@ -137,33 +145,33 @@ const directNeighbours2D = [ * - LEFT/RIGHT */ const directNeighbours3D = [ - Adjacent3dPos.xPy0z0, - Adjacent3dPos.xMy0z0, // right, left - Adjacent3dPos.x0yPz0, - Adjacent3dPos.x0yMz0, // top, bottom - Adjacent3dPos.x0y0zP, - Adjacent3dPos.x0y0zM, // front, back + VolumeNeighbour.xPy0z0, + VolumeNeighbour.xMy0z0, // right, left + VolumeNeighbour.x0yPz0, + VolumeNeighbour.x0yMz0, // top, bottom + VolumeNeighbour.x0y0zP, + VolumeNeighbour.x0y0zM, // front, back ] -const getAdjacent2dCoords = (pos: Vector2, dir: Adjacent2dPos): Vector2 => { +const getAdjacent2dCoords = (pos: Vector2, dir: SurfaceNeighbour): Vector2 => { switch (dir) { - case Adjacent2dPos.center: + case SurfaceNeighbour.center: return pos.clone() - case Adjacent2dPos.left: + case SurfaceNeighbour.left: return pos.clone().add(new Vector2(-1, 0)) - case Adjacent2dPos.right: + case SurfaceNeighbour.right: return pos.clone().add(new Vector2(1, 0)) - case Adjacent2dPos.top: + case SurfaceNeighbour.top: return pos.clone().add(new Vector2(0, 1)) - case Adjacent2dPos.bottom: + case SurfaceNeighbour.bottom: return pos.clone().add(new Vector2(0, -1)) - case Adjacent2dPos.topleft: + case SurfaceNeighbour.topleft: return pos.clone().add(new Vector2(-1, 1)) - case Adjacent2dPos.topright: + case SurfaceNeighbour.topright: return pos.clone().add(new Vector2(1, 1)) - case Adjacent2dPos.bottomright: + case SurfaceNeighbour.bottomright: return pos.clone().add(new Vector2(-1, -1)) - case Adjacent2dPos.bottomleft: + case SurfaceNeighbour.bottomleft: return pos.clone().add(new Vector2(1, -1)) } } @@ -174,59 +182,59 @@ const getAdjacent2dCoords = (pos: Vector2, dir: Adjacent2dPos): Vector2 => { * @param dir neighbour identifier * @returns */ -const getAdjacent3dCoords = (pos: Vector3, dir: Adjacent3dPos): Vector3 => { +const getAdjacent3dCoords = (pos: Vector3, dir: VolumeNeighbour): Vector3 => { switch (dir) { - case Adjacent3dPos.xMyMzM: + case VolumeNeighbour.xMyMzM: return pos.clone().add(new Vector3(-1, -1, -1)) - case Adjacent3dPos.xMyMz0: + case VolumeNeighbour.xMyMz0: return pos.clone().add(new Vector3(-1, -1, 0)) - case Adjacent3dPos.xMyMzP: + case VolumeNeighbour.xMyMzP: return pos.clone().add(new Vector3(-1, -1, 1)) - case Adjacent3dPos.xMy0zM: + case VolumeNeighbour.xMy0zM: return pos.clone().add(new Vector3(-1, 0, -1)) - case Adjacent3dPos.xMy0z0: + case VolumeNeighbour.xMy0z0: return pos.clone().add(new Vector3(-1, 0, 0)) - case Adjacent3dPos.xMy0zP: + case VolumeNeighbour.xMy0zP: return pos.clone().add(new Vector3(-1, 0, 1)) - case Adjacent3dPos.xMyPzM: + case VolumeNeighbour.xMyPzM: return pos.clone().add(new Vector3(-1, 1, -1)) - case Adjacent3dPos.xMyPz0: + case VolumeNeighbour.xMyPz0: return pos.clone().add(new Vector3(-1, 1, 0)) - case Adjacent3dPos.xMyPzP: + case VolumeNeighbour.xMyPzP: return pos.clone().add(new Vector3(-1, 1, 1)) - case Adjacent3dPos.x0yMzM: + case VolumeNeighbour.x0yMzM: return pos.clone().add(new Vector3(0, -1, -1)) - case Adjacent3dPos.x0yMz0: + case VolumeNeighbour.x0yMz0: return pos.clone().add(new Vector3(0, -1, 0)) - case Adjacent3dPos.x0yMzP: + case VolumeNeighbour.x0yMzP: return pos.clone().add(new Vector3(0, -1, 1)) - case Adjacent3dPos.x0y0zM: + case VolumeNeighbour.x0y0zM: return pos.clone().add(new Vector3(0, 0, -1)) - case Adjacent3dPos.x0y0zP: + case VolumeNeighbour.x0y0zP: return pos.clone().add(new Vector3(0, 0, 1)) - case Adjacent3dPos.x0yPzM: + case VolumeNeighbour.x0yPzM: return pos.clone().add(new Vector3(0, 1, -1)) - case Adjacent3dPos.x0yPz0: + case VolumeNeighbour.x0yPz0: return pos.clone().add(new Vector3(0, 1, 0)) - case Adjacent3dPos.x0yPzP: + case VolumeNeighbour.x0yPzP: return pos.clone().add(new Vector3(0, 1, 1)) - case Adjacent3dPos.xPyMzM: + case VolumeNeighbour.xPyMzM: return pos.clone().add(new Vector3(1, -1, -1)) - case Adjacent3dPos.xPyMz0: + case VolumeNeighbour.xPyMz0: return pos.clone().add(new Vector3(1, -1, 0)) - case Adjacent3dPos.xPyMzP: + case VolumeNeighbour.xPyMzP: return pos.clone().add(new Vector3(1, -1, 1)) - case Adjacent3dPos.xPy0zM: + case VolumeNeighbour.xPy0zM: return pos.clone().add(new Vector3(1, 0, -1)) - case Adjacent3dPos.xPy0z0: + case VolumeNeighbour.xPy0z0: return pos.clone().add(new Vector3(1, 0, 0)) - case Adjacent3dPos.xPy0zP: + case VolumeNeighbour.xPy0zP: return pos.clone().add(new Vector3(1, 0, 1)) - case Adjacent3dPos.xPyPzM: + case VolumeNeighbour.xPyPzM: return pos.clone().add(new Vector3(1, 1, -1)) - case Adjacent3dPos.xPyPz0: + case VolumeNeighbour.xPyPz0: return pos.clone().add(new Vector3(1, 1, 0)) - case Adjacent3dPos.xPyPzP: + case VolumeNeighbour.xPyPzP: return pos.clone().add(new Vector3(1, 1, 1)) } } @@ -237,7 +245,7 @@ const getNeighbours2D = ( ): Vector2[] => { const neighbours = directNeighboursOnly ? directNeighbours2D - : Object.values(Adjacent2dPos).filter(v => !isNaN(Number(v))) + : Object.values(SurfaceNeighbour).filter(v => !isNaN(Number(v))) return neighbours.map(type => getAdjacent2dCoords(pos, type as number)) } @@ -247,17 +255,17 @@ const getNeighbours3D = ( ): Vector3[] => { const neighbours = directNeighboursOnly ? directNeighbours3D - : Object.values(Adjacent3dPos).filter(v => !isNaN(Number(v))) + : Object.values(VolumeNeighbour).filter(v => !isNaN(Number(v))) return neighbours.map(type => getAdjacent3dCoords(pos, type as number)) } -const getBoundsCornerPoints = (bounds: Box2) => { +const getPatchBoundingPoints = (bounds: Box2) => { const { min: xMyM, max: xPyP } = bounds const xMyP = xMyM.clone() xMyP.y = xPyP.y const xPyM = xMyM.clone() xPyM.x = xPyP.x - const points = [xMyM, xMyP, xPyM, xPyP] + const points: PatchBoundingPoints = { xMyM, xMyP, xPyM, xPyP } return points } @@ -461,13 +469,14 @@ const chunkBoxFromKey = (chunkKey: string, chunkDims: Vector3) => { export { roundToDec, vectRoundToDec, + smoothstep, clamp, findMatchingRange, bilinearInterpolation, getNeighbours2D, getNeighbours3D, bboxContainsPointXZ, - getBoundsCornerPoints, + getPatchBoundingPoints, parseThreeStub, asVect2, asVect3, diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index f72fbaf..2248570 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -7,6 +7,7 @@ import * as Utils from '../common/utils' import { ProcLayer } from './ProcLayer' import { BiomeConfigs, BiomeConfKey, NoiseLevelConf } from '../common/types' +import { smoothstep } from 'three/src/math/MathUtils' export enum BlockType { NONE, @@ -28,6 +29,36 @@ export enum BlockType { DBG_GREEN, } +enum Level { + LOW = 'low', + MID = 'mid', + HIGH = 'high' +} + +enum HeatLevel { + COLD = 'cold', + TEMPERATE = 'temperate', + HOT = 'hot' +} + +enum RainLevel { + DRY = 'dry', + MODERATE = 'mod', + WET = 'wet' +} + +const heatLevelMappings: Record = { + [Level.LOW]: HeatLevel.COLD, + [Level.MID]: HeatLevel.TEMPERATE, + [Level.HIGH]: HeatLevel.HOT +} + +const rainLevelMappings: Record = { + [Level.LOW]: RainLevel.DRY, + [Level.MID]: RainLevel.MODERATE, + [Level.HIGH]: RainLevel.WET +} + export enum BiomeType { Temperate = 'temperate', Artic = 'artic', @@ -35,37 +66,37 @@ export enum BiomeType { // Tropical = 'tropical', } -enum Heat { - Cold = 'cold', - Temperate = 'temperate', - Hot = 'hot', -} +type Contribution = Record +type HeatContributions = Record +type RainContributions = Record -enum Rain { - Dry = 'dry', - Moderate = 'moderate', - Wet = 'wet', +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 + mappedContribution[targetKey] = val + return mappedContribution + }) + return mappedContribution } -type HeatContribs = Record -type RainContribs = Record export type BiomeInfluence = Record -const BiomesMapping: Record> = { - [Heat.Cold]: { - [Rain.Dry]: BiomeType.Artic, - [Rain.Moderate]: BiomeType.Artic, - [Rain.Wet]: BiomeType.Artic, +const BiomesMapping: Record> = { + [HeatLevel.COLD]: { + [RainLevel.DRY]: BiomeType.Artic, + [RainLevel.MODERATE]: BiomeType.Artic, + [RainLevel.WET]: BiomeType.Artic, }, - [Heat.Temperate]: { - [Rain.Dry]: BiomeType.Temperate, // TODO - [Rain.Moderate]: BiomeType.Temperate, - [Rain.Wet]: BiomeType.Temperate, // TODO + [HeatLevel.TEMPERATE]: { + [RainLevel.DRY]: BiomeType.Temperate, // TODO + [RainLevel.MODERATE]: BiomeType.Temperate, + [RainLevel.WET]: BiomeType.Temperate, // TODO }, - [Heat.Hot]: { - [Rain.Dry]: BiomeType.Desert, - [Rain.Moderate]: BiomeType.Desert, - [Rain.Wet]: BiomeType.Desert, // TODO BiomeType.Tropical, + [HeatLevel.HOT]: { + [RainLevel.DRY]: BiomeType.Desert, + [RainLevel.MODERATE]: BiomeType.Desert, + [RainLevel.WET]: BiomeType.Desert, // TODO BiomeType.Tropical, }, } @@ -83,11 +114,17 @@ export class Biome { mappings = {} as BiomeConfigs posRandomizer: ProcLayer - triggerLevels = { - low: 0.3, - mid_low: 0.4, - mid: 0.5, - mid_high: 0.6, + /** + * val < lowToMid=> LOW = 1 + * lowToMid < val < mid => LOW decrease, MID increase + * mid < val < midToHigh => MID = 1 + * midToHigh < val < high => MID decrease, HIGH increase + * val > hight => HIGH = 1 + */ + steps = { + lowToMid: 0.3, + mid: 0.4, + midToHigh: 0.6, high: 0.7, } @@ -137,85 +174,71 @@ export class Biome { return mainBiome as BiomeType } - getBiomeInfluence(pos: Vector2 | Vector3): BiomeInfluence { - const heatContribs: HeatContribs = { - [Heat.Cold]: 0, - [Heat.Temperate]: 0, - [Heat.Hot]: 0, + calculateContributions(value: number) { + const { steps } = this + + const contributions = { + low: 0, + mid: 0, + high: 0, + }; + + // LOW + if (value < steps.lowToMid) { + 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 rainContribs: RainContribs = { - [Rain.Dry]: 0, - [Rain.Moderate]: 0, - [Rain.Wet]: 0, + // MID + else if (value < steps.midToHigh) { + 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; + } + // HIGH + else { + contributions.high = 1; } + + // if (value < 0.5) { + // const level = smoothstep(value, steps.lowToMid, steps.mid) + // contributions.low = 1 - level + // contributions.mid = level + // } else { + // const heatLevel = smoothstep(value, steps.midToHigh, steps.high) + // contributions.mid = 1 - heatLevel + // contributions.high = heatLevel + // } + + return contributions; + } + + getBiomeInfluence(pos: Vector2 | Vector3): BiomeInfluence { const biomeContribs: BiomeInfluence = { [BiomeType.Temperate]: 0, [BiomeType.Artic]: 0, [BiomeType.Desert]: 0, } - const { low, mid_low, mid_high, high } = this.triggerLevels + const heatVal = this.heatmap.eval(pos) // Utils.roundToDec(this.heatmap.eval(pos), 2) const rainVal = this.rainmap.eval(pos) // Utils.roundToDec(this.rainmap.eval(pos), 2) + let contrib = this.calculateContributions(heatVal) + const heatContributions = translateContribution(contrib, heatLevelMappings) + contrib = this.calculateContributions(rainVal) + const rainContributions = translateContribution(contrib, rainLevelMappings) - // TEMPERATURE - // cold - if (heatVal <= low) { - heatContribs.cold = 1 - } - // cold to temperate transition - else if (heatVal <= mid_low) { - heatContribs.temperate = - (heatVal - low) / - (mid_low - low) - heatContribs.cold = 1 - heatContribs.temperate - } - // temperate - else if (heatVal <= mid_high) { - heatContribs.temperate = 1 - } - // temperate to hot transition - else if (heatVal <= high) { - heatContribs.hot = - (heatVal - mid_high) / - (high - mid_high) - heatContribs.temperate = 1 - heatContribs.hot - } - // hot - else { - heatContribs.hot = 1 - } - - // HUMIDITY - // dry - if (rainVal <= low) { - rainContribs.dry = 1 - } - // dry => moderate transition - else if (rainVal <= mid_low) { - rainContribs.moderate = - (rainVal - low) / - (mid_low - low) - rainContribs.dry = 1 - rainContribs.moderate - } - // moderate - else if (rainVal <= mid_high) { - rainContribs.moderate = 1 - } - // moderate to wet transition - else if (rainVal <= high) { - rainContribs.wet = - (rainVal - mid_high) / - (high - mid_high) - rainContribs.moderate = 1 - rainContribs.wet - } - // wet - else { - rainContribs.wet = 1 - } - Object.entries(heatContribs).forEach(([k1, v1]) => { - Object.entries(rainContribs).forEach(([k2, v2]) => { - const biomeType = BiomesMapping[k1 as Heat][k2 as Rain] + Object.entries(heatContributions).forEach(([k1, v1]) => { + Object.entries(rainContributions).forEach(([k2, v2]) => { + const biomeType = BiomesMapping[k1 as HeatLevel][k2 as RainLevel] biomeContribs[biomeType] += v1 * v2 }) }) From a8cba897f34025068d0c7989222003e21e5d4e67 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 11 Oct 2024 17:15:28 +0000 Subject: [PATCH 08/13] feat: biome flora settings with multiple items per spawn distribution --- src/api/world-compute.ts | 49 ++++++++----- src/datacontainers/RandomDistributionMap.ts | 79 ++++++++++++--------- 2 files changed, 75 insertions(+), 53 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 84e8d9b..cc87fae 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -189,24 +189,37 @@ export const retrieveOvergroundItems = async (bounds: Box2) => { const block = computeGroundBlock(asVect3(pos)) blockConfigs[block.confKey] = Biome.instance.indexedConf.get(block.confKey)?.data }) - const spawnedItems = {} - // Object.values(blockConfigs).forEach(blockConf => blockConf?.entities?.forEach(itemType => spawnedItems[itemType] = [])) - // Object.keys(spawnedItems).forEach(itemType => { - // const spawnablePlaces = ItemsInventory.querySpawnedEntities( - // itemType, - // bounds, - // ) - // spawnablePlaces.forEach(itemPos => { - // // confirm entities and add spawn elevation - // const blockConf = Biome.instance.indexedConf.get(itemBlock.confKey)?.data - // if (blockConf?.flora)//find(val => val === itemType)) - // { - // confirmedItems[itemType] = confirmedItems[itemType] || []; - // (confirmedItems[itemType] as Vector3[]).push(asVect3(itemPos, itemBlock.level)) - // } - // }) - // }) - return spawnedItems +const itemsSpawnMap = new PseudoDistributionMap() + const itemsWeightedList: ItemType[] = [] + const itemDims = new Vector3(10, 13, 10) + Object.values(blockConfigs).forEach((blockConf: NoiseLevelConf) => { + // blockConf?.entities?.forEach(itemType => { + // spawnedItems[itemType] = [] + // }) + Object.entries(blockConf.flora || {}).forEach(([itemType, itemWeight]) => { + // build weighted items array + while (itemWeight > 0) { + itemsWeightedList.push(itemType) + itemWeight-- + } + }) + }) + const spawnedItems = itemsSpawnMap.querySpawnedItems(bounds, asVect2(itemDims), itemsWeightedList) + const confirmedItems: Record = {} + Object.entries(spawnedItems) + .forEach(([itemType, itemSpawnLocs]) => { + itemSpawnLocs.map(itemPos => { + const itemBlock = computeGroundBlock(asVect3(itemPos)) + // confirm entities and add spawn elevation + const blockConf = Biome.instance.indexedConf.get(itemBlock.confKey)?.data + if (blockConf?.flora)//find(val => val === itemType)) + { + confirmedItems[itemType] = confirmedItems[itemType] || []; + (confirmedItems[itemType] as Vector3[]).push(asVect3(itemPos, itemBlock.level)) + } + }) + }) + return confirmedItems } /** diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 8a4c412..7a151b0 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -6,8 +6,9 @@ import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' import { WorldConf } from '../index' import { getPatchIds } from '../common/utils' +import { ItemType } from '../misc/ItemsInventory' -// import { Adjacent2dPos } from '../common/types' +// import { SurfaceNeighbour } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' const probabilityThreshold = Math.pow(2, 8) @@ -42,7 +43,7 @@ export class PseudoDistributionMap { this.densityMap = new ProcLayer(distParams.aleaSeed || '') } - spawnProbabilityEval(pos: Vector2) { + spawnProbabilityEval = (pos: Vector2) => { const maxCount = 1 // 16 * Math.round(Math.exp(10)) const val = this.densityMap?.eval(pos) const adjustedVal = val @@ -51,18 +52,6 @@ export class PseudoDistributionMap { return adjustedVal } - hasSpawned(itemPos: Vector2, spawnProbabilty?: number) { - // eval spawn probability at entity center - spawnProbabilty = - spawnProbabilty && !isNaN(spawnProbabilty) - ? spawnProbabilty - : this.spawnProbabilityEval(itemPos) - const itemId = itemPos.x + ':' + itemPos.y - const prng = alea(itemId) - const hasSpawned = prng() * spawnProbabilty < probabilityThreshold - return hasSpawned - } - /** * querying/iterating randomly distributed items at block level or from custom bounds * @param entityShaper @@ -71,37 +60,57 @@ export class PseudoDistributionMap { * @returns all entities locations overlapping with input point or bounds */ querySpawnLocations( - testRange: Vector2 | Box2, - overlapsTest: (testRange: Box2, entityPos: Vector2) => boolean, - spawnProbabilityOverride?: (entityPos?: Vector2) => number, + queryBoxOrLoc: Vector2 | Box2, + itemDims: Vector2, // entityMask = (_entity: EntityData) => false ) { - const testBox = - testRange instanceof Box2 - ? testRange - : new Box2().setFromPoints([testRange]) + const queryBox = + queryBoxOrLoc instanceof Box2 + ? queryBoxOrLoc + : new Box2().setFromPoints([queryBoxOrLoc]) // const offset = testBox.min.clone().divide(this.patchDimensions).floor().multiply(this.patchDimensions) // const localTestBox = testBox.clone().translate(offset.clone().negate()) // const overlappingEntities = this.repeatedPattern.elements // .filter(entityPos => overlapsTest(localTestBox, entityPos)) // .map(relativePos => relativePos.clone().add(offset)) - const overlappingEntities: Vector2[] = [] - const patchIds = getPatchIds(testBox, this.patchDimensions) + const spawnLocations: Vector2[] = [] + const patchIds = getPatchIds(queryBox, this.patchDimensions) for (const patchId of patchIds) { const offset = patchId.clone().multiply(this.patchDimensions) - const localTestBox = testBox.clone().translate(offset.clone().negate()) + const localRegionQuery = queryBox.clone().translate(offset.clone().negate()) // look for entities overlapping with input point or area - for (const relativePos of this.repeatedPattern.elements) { - if (overlapsTest(localTestBox, relativePos)) { - const entityPos = relativePos.clone().add(offset) - overlappingEntities.push(entityPos) + for (const spawnLocalPos of this.repeatedPattern.elements) { + // eval spawn probability at entity center + const spawnBox = new Box2().setFromCenterAndSize(spawnLocalPos, itemDims) + if (spawnBox.intersectsBox(localRegionQuery)) { + const itemPos = spawnLocalPos.clone().add(offset) + spawnLocations.push(itemPos) } } } - const spawnedEntities = overlappingEntities.filter(entityPos => - this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos)), - ) - return spawnedEntities + return spawnLocations + } + + querySpawnedItems(queryBoxOrLoc: Vector2 | Box2, itemDims: Vector2, spawnableItems: ItemType[], spawnProbabilityEval = this.spawnProbabilityEval) { + const spawnedItems: Record = {} + const itemsCount = spawnableItems.length + const spawnablePlaces = this.querySpawnLocations(queryBoxOrLoc, itemDims) + 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 spawnedItems } // /** @@ -123,10 +132,10 @@ export class OverlappingEntitiesMap { static biomeMapsLookup: Record = {} // getAdjacentEntities() { // const adjacentEntities = [] - // const adjacentKeys = Object.values(Adjacent2dPos) - // .filter(v => !isNaN(Number(v)) && v !== Adjacent2dPos.center) + // const adjacentKeys = Object.values(SurfaceNeighbour) + // .filter(v => !isNaN(Number(v)) && v !== SurfaceNeighbour.center) // .map(adjKey => { - // const adjCoords = getAdjacent2dCoords(patchCoords, adjKey as Adjacent2dPos) + // const adjCoords = getAdjacent2dCoords(patchCoords, adjKey as SurfaceNeighbour) // const mapKey = `map_${adjCoords.x % repeatPeriod}_${adjCoords.y % repeatPeriod}` // return mapKey // }) From c5e0e97aced8880b50a886a0b9a31505f5784396 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 17 Oct 2024 16:37:25 +0000 Subject: [PATCH 09/13] feat: refactor/fix LOD, boards, block type definitions, builtin ground cache, renaming --- src/api/WorldComputeProxy.ts | 25 +- src/api/world-compute.ts | 326 ++++++++++-------- src/common/types.ts | 45 +-- .../{GroundMap.ts => GroundCache.ts} | 164 ++++++--- src/datacontainers/RandomDistributionMap.ts | 69 ++-- src/feats/BoardContainer.ts | 52 +-- src/index.ts | 10 +- src/misc/ItemsInventory.ts | 21 +- src/procgen/Biome.ts | 65 ++-- src/procgen/BlueNoisePattern.ts | 13 +- src/third-party/nbt_custom.ts | 32 +- src/tools/ChunkFactory.ts | 40 +-- src/tools/ProceduralGenerators.ts | 12 +- src/tools/SchematicLoader.ts | 17 +- 14 files changed, 488 insertions(+), 403 deletions(-) rename src/datacontainers/{GroundMap.ts => GroundCache.ts} (71%) diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index 9f525fa..32fe2d2 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,4 +1,4 @@ -import { Box2, Vector3 } from 'three' +import { Box2, Vector2 } from 'three' import { Block, PatchKey } from '../common/types' import { GroundPatch, WorldCompute, WorldUtils } from '../index' @@ -17,7 +17,7 @@ export type ComputeApiParams = Partial<{ }> /** - * Frontend exposing world APIs and proxying requests to internal modules: world-compute, world-cache, + * 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 */ @@ -72,7 +72,7 @@ export class WorldComputeProxy { } async computeBlocksBatch( - blockPosBatch: Vector3[], + blockPosBatch: Vector2[], params = { includeEntitiesBlocks: false }, ) { const blocks = !this.worker @@ -91,13 +91,6 @@ export class WorldComputeProxy { return blocks } - // *iterEntitiesBaking(entityKeys: EntityKey[]) { - // for (const entityKey of entityKeys) { - // const entityChunk = WorldCompute.bakeChunkEntity(entityKey) - // yield entityChunk - // } - // } - async queryOvergroundItems(queriedRegion: Box2) { const overgroundItems = !this.worker ? WorldCompute.retrieveOvergroundItems(queriedRegion) @@ -132,18 +125,6 @@ export class WorldComputeProxy { return patchStub } - async bakeEntities(queriedRange: Box2) { - // const entityChunks = !this.worker - // ? WorldCompute.queryBakeEntities(queriedRange) - // : await this.workerCall(ComputeApiCall.BakeEntities, [ - // queriedRange, - // ])?.then((entityChunks: EntityChunkStub[]) => - // // parse worker's data to recreate original objects - // entityChunks.map(chunkStub => EntityChunk.fromStub(chunkStub)), - // ) - return []//entityChunks - } - // async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) { // const boardData = !this.worker ? // WorldCompute.computeBoardData(boardCenter, boardParams, lastBoardBounds) : diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index cc87fae..29b26a3 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -2,12 +2,19 @@ import { Box2, Vector2, Vector3 } from 'three' import { GroundPatch, ItemsInventory, PseudoDistributionMap, WorldConf } from '../index' import { Biome, BiomeInfluence, BiomeType, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { Block, NoiseLevelConf, PatchBoundId, PatchKey } from '../common/types' +import { BiomeLandscapeElement, Block, 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 { 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 defaultItemDims = new Vector3(10, 13, 10) + /** * Brain of the world runnable in separate worker * Support for: @@ -17,60 +24,36 @@ type PatchBoundingBiomes = Record */ /** - * - * @param blockPosBatch - * @param params - * @returns + * Common */ -export const computeBlocksBatch = ( - blockPosBatch: Vector2[], - params = { includeEntitiesBlocks: false }, -) => { - const { includeEntitiesBlocks } = params - // sort blocks by patch - const blocksByPatch: Record = { +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]) + return !different } - blockPosBatch.forEach(pos => { - const patchKey = serializePatchId(getPatchId(pos, WorldConf.regularPatchDimensions)) - blocksByPatch[patchKey] = blocksByPatch[patchKey] || [] - blocksByPatch[patchKey]?.push(pos) + const boundsPoints = getPatchBoundingPoints(bounds) + const boundsInfluences = {} as PatchBoundingBiomes + [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 }) - Object.entries(blocksByPatch).forEach(([patchKey, patchBlocks]) => { + const allEquals = equals(boundsInfluences[xMyM], boundsInfluences[xPyM]) + && equals(boundsInfluences[xMyM], boundsInfluences[xMyP]) + && equals(boundsInfluences[xMyM], boundsInfluences[xPyP]) + return allEquals ? boundsInfluences[xMyM] : boundsInfluences +} - }) - const blocksBatch = blockPosBatch.map(({ x, z }) => { - const blockPos = new Vector3(x, 0, z) - const blockData = computeGroundBlock(blockPos) - const { spawnableItems } = blockData - const queriedLoc = new Box2().setFromPoints([asVect2(blockPos)]) - queriedLoc.max.addScalar(1) - false && includeEntitiesBlocks && spawnableItems.forEach(itemType => { - // several (overlapping) objects may be found at queried position - const [spawnedEntity] = ItemsInventory.querySpawnedEntities(itemType, queriedLoc) - - // const [foundEntity] = queryEntities(spawnRange).map(entityData => { - // const { min, max } = entityData.bbox - // const custChunkBox = asBox3(entityRange) - // custChunkBox.min.y = min.y - // custChunkBox.max.y = max.y - // return new EntityChunk(entityData, custChunkBox) - // }) - // foundEntity. - const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) - if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { - blockData.level += lastBlockIndex - blockData.type = blocksBuffer[lastBlockIndex] as BlockType - } - }) - blockPos.y = blockData.level - const block: Block = { - pos: blockPos, - data: blockData, - } - return block - }) - return blocksBatch +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) + } + return boundingBiomes } export const computeGroundBlock = (blockPos: Vector3, biomeInfluence?: BiomeInfluence) => { @@ -78,7 +61,7 @@ export const computeGroundBlock = (blockPos: Vector3, biomeInfluence?: BiomeInfl // 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 NoiseLevelConf + const noiseLevel = Biome.instance.getBiomeConf(rawVal, biomeType) as BiomeLandscapeElement const currLevelConf = noiseLevel.data const prevLevelConf = noiseLevel.prev?.data const nextLevelConf = noiseLevel.next?.data @@ -91,30 +74,96 @@ export const computeGroundBlock = (blockPos: Vector3, biomeInfluence?: BiomeInfl biomeInfluence, ) // const pos = new Vector3(blockPos.x, level, blockPos.z) - 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) - 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 - const type = variation > threshold && prevType ? prevType : currLevelConf.type + 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) + 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 + } + if (!type) { console.log(currLevelConf) } - const spawnableItems = currLevelConf.entities - // const entityType = blockTypes.entities?.[0] as EntityType - // let offset = 0 - // if (lastBlock && entityType) { // } // level += offset - const output = { level, type, spawnableItems, confKey } + const output = { level, type, confKey } return output } +/** + * Individual blocks + */ + +/** + * + * @param blockPosBatch + * @param params + * @returns + */ +export const computeBlocksBatch = async ( + blockPosBatch: Vector2[], + params = { includeEntitiesBlocks: false }, +) => { + // sort blocks by patch + const blocksByPatch: Record = {} + const blocksBatch = blockPosBatch.map(pos => { + const patchKey = serializePatchId(getPatchId(pos, WorldConf.regularPatchDimensions)) + const block = { + pos: asVect3(pos), + data: null, + } + blocksByPatch[patchKey] = blocksByPatch[patchKey] || [] + blocksByPatch[patchKey]?.push(block as any) + return block + }) + for await (const [patchKey, patchBlocks] of Object.entries(blocksByPatch)) { + 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) + 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.pos.y = block.data.level + } + } + + // const blocksBatch = blockPosBatch.map((pos) => { + // const blockPos = asVect3(pos) + // const blockData = computeGroundBlock(blockPos) + // const { spawnableItems } = blockData + // const queriedLoc = new Box2().setFromPoints([asVect2(blockPos)]) + // queriedLoc.max.addScalar(1) + // false && includeEntitiesBlocks && spawnableItems.forEach(itemType => { + // // several (overlapping) objects may be found at queried position + // const [spawnedEntity] = ItemsInventory.querySpawnedEntities(itemType, queriedLoc) + // const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) + // if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { + // blockData.level += lastBlockIndex + // blockData.type = blocksBuffer[lastBlockIndex] as BlockType + // } + // }) + // blockPos.y = blockData.level + // const block: Block = { + // pos: blockPos, + // data: blockData, + // } + // return block + // }) + return blocksBatch +} + /** * Patch requests */ @@ -127,34 +176,6 @@ export const bakePatch = (boundsOrPatchKey: PatchKey | Box2) => { return groundLayer } -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]) - return !different - } - const boundsPoints = getPatchBoundingPoints(bounds) - const boundsInfluences = {} as PatchBoundingBiomes - [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]) - 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) - } -} - // Patch ground layer export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { const groundPatch = new GroundPatch(boundsOrPatchKey) @@ -170,68 +191,87 @@ export const bakeGroundLayer = (boundsOrPatchKey: PatchKey | Box2) => { // 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 blockData = computeGroundBlock(block.pos, 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) groundPatch.writeBlockData(blockIndex, blockData) - groundPatch.blockConfigs[blockData.confKey] = true blockIndex++ } return groundPatch } +// Patch overground layer export const retrieveOvergroundItems = async (bounds: Box2) => { - // spawnable items based on soil type found in this specific region - const blockConfigs: any = {} - const patchBoundingPoints = getPatchBoundingPoints(bounds); - // eval configs at each patch corners - Object.values(patchBoundingPoints).forEach(pos => { - const block = computeGroundBlock(asVect3(pos)) - blockConfigs[block.confKey] = Biome.instance.indexedConf.get(block.confKey)?.data - }) -const itemsSpawnMap = new PseudoDistributionMap() - const itemsWeightedList: ItemType[] = [] - const itemDims = new Vector3(10, 13, 10) - Object.values(blockConfigs).forEach((blockConf: NoiseLevelConf) => { - // blockConf?.entities?.forEach(itemType => { - // spawnedItems[itemType] = [] - // }) - Object.entries(blockConf.flora || {}).forEach(([itemType, itemWeight]) => { - // build weighted items array - while (itemWeight > 0) { - itemsWeightedList.push(itemType) - itemWeight-- - } - }) - }) - const spawnedItems = itemsSpawnMap.querySpawnedItems(bounds, asVect2(itemDims), itemsWeightedList) - const confirmedItems: Record = {} - Object.entries(spawnedItems) - .forEach(([itemType, itemSpawnLocs]) => { - itemSpawnLocs.map(itemPos => { - const itemBlock = computeGroundBlock(asVect3(itemPos)) - // confirm entities and add spawn elevation - const blockConf = Biome.instance.indexedConf.get(itemBlock.confKey)?.data - if (blockConf?.flora)//find(val => val === itemType)) - { - confirmedItems[itemType] = confirmedItems[itemType] || []; - (confirmedItems[itemType] as Vector3[]).push(asVect3(itemPos, itemBlock.level)) + const boundsBiomeInfluences = getBiomeBoundsInfluences(bounds) + + const spawnedItems: Record = {} + const spawnPlaces = defaultSpawnMap.querySpawnLocations(bounds, asVect2(defaultItemDims)) + spawnPlaces.map(pos => { + const blockBiome = getBlockBiome(pos, bounds, boundsBiomeInfluences) + const { confKey, level } = computeGroundBlock(asVect3(pos), blockBiome) + const weightedItems = Biome.instance.indexedConf.get(confKey)?.data?.flora + if (weightedItems) { + const spawnableTypes: ItemType[] = [] + Object.entries(weightedItems).forEach(([itemType, spawnWeight]) => { + while (spawnWeight > 0) { + spawnableTypes.push(itemType) + spawnWeight-- } }) - }) - return confirmedItems + const itemType = defaultSpawnMap.getSpawnedItem(pos, spawnableTypes) as ItemType + if (itemType) { + spawnedItems[itemType] = spawnedItems[itemType] || [] + spawnedItems[itemType]?.push(asVect3(pos, level)) + } + } + }) + return spawnedItems } -/** - * patch is an assembly of several layers - * - ground - * - underground caverns - * - overground objects - */ -export const bakePatchLayers = () => { } -export const bakePatchGroundLayer = () => { } -export const bakePatchUndergroundLayer = () => { } // or caverns -export const bakePatchOvergroundLayer = (boundsOrPatchKey: PatchKey | Box2, itemType: WorldItem) => { } +export const queryLastBlockData = async (queriedLoc: Vector2) => { + const lastBlockData: BlockData = { + level: 0, + type: 0 + } + const spawnPlaces = defaultSpawnMap.querySpawnLocations(queriedLoc, asVect2(defaultItemDims)) + for await (const spawnOrigin of spawnPlaces) { + 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 spawnableItems: ItemType[] = [] + for (let [itemType, spawnWeight] of Object.entries(spawnableTypes || {})) { + while (spawnWeight > 0) { + spawnableItems.push(itemType) + spawnWeight-- + } + } + const itemType = defaultSpawnMap.getSpawnedItem(spawnOrigin, spawnableItems) as ItemType + if (itemType && 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() + // const localCenter = spawnOrigin.clone().sub(chunkOrigin) + const localCenter = itemChunk.toLocalPos(asVect3(spawnOrigin)) + const blocksBuffer = itemChunk.readBuffer(asVect2(localCenter)) + // find last block in buffer and override block level accordingly + let lastIndex = blocksBuffer ? blocksBuffer.length - 1 : 0 + while (lastIndex > 0 && !blocksBuffer[lastIndex]) lastIndex-- + const lastLevel = level + lastIndex + const type = blocksBuffer?.[lastIndex] + if (type && lastLevel > lastBlockData.level) { + lastBlockData.level = lastLevel + lastBlockData.type = type as BlockType + } + } + } + } + return lastBlockData +} // Battle board diff --git a/src/common/types.ts b/src/common/types.ts index d71f7ee..de24359 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,6 +1,7 @@ -import { Box3, Vector2, Vector3 } from 'three' +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' @@ -42,10 +43,10 @@ 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 @@ -135,41 +136,25 @@ export type ProcLayerExtCfg = { // MOUNTAINS_TOP, // } -export type MetadataFields = { - key: BiomeConfKey, +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 - entities: string[] // which entities can spawn + flora?: Record, fadein: any, fadeout: any } +// Biome landscapes mappings +export type BiomeLandscapes = Record> +export type BiomeConfigs = Record +export type BiomeLandscapeElement = LinkedList -export type NoiseLevelMappings = Record> -export type BiomeConfigs = Record -export type NoiseLevelConf = LinkedList - -export enum EntityType { - NONE = '', - TREE_APPLE = 'apple_tree', - TREE_PINE = 'pine_tree', -} - -export type EntityData = { - type: EntityType - bbox: Box3 - params: { - radius: number - size: number - } -} - -export type NoiseLevelId = string // an arbitrary name given to help identify noise level -export type BiomeConfKey = string // combination of noiseLevelId and biomeType -export type EntityKey = string +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 diff --git a/src/datacontainers/GroundMap.ts b/src/datacontainers/GroundCache.ts similarity index 71% rename from src/datacontainers/GroundMap.ts rename to src/datacontainers/GroundCache.ts index 59464d8..5488256 100644 --- a/src/datacontainers/GroundMap.ts +++ b/src/datacontainers/GroundCache.ts @@ -1,11 +1,22 @@ +/** + * Allows precaching patches around position, with new patches automatically computed + * when position is updated + * Previous patches can be simply removed, or kept until condition is met: + * cache size exceeds, LRU, .. + */ import { Box2, Vector2 } from 'three' -import { PatchKey } from '../common/types' -import { getPatchIds, serializePatchId } from '../common/utils' -import { GroundPatch, WorldConf } from '../index' +import { PatchBlock, PatchKey } from '../common/types' +import { getPatchId, getPatchIds, serializePatchId } from '../common/utils' +import { GroundPatch, PatchContainer, WorldConf } from '../index' -export class PatchesContainer { - patchLookup: Record = {} +export class PatchesContainer> { + patchLookup: Record = {} + patchDimensions + + constructor() { + this.patchDimensions = WorldConf.regularPatchDimensions + } get keys() { return Object.keys(this.patchLookup) @@ -15,29 +26,118 @@ export class PatchesContainer { return Object.values(this.patchLookup) } + getOverlappingPatches(inputBounds: Box2) { + return this.patches.filter(patch => patch.isOverlapping(inputBounds)) + } + + findPatch(blockPos: Vector2) { + // const res = this.patches.find(patch => patch.containsPoint(blockPos)) + // return res + blockPos.floor() + // compute patch key from which blocks belongs to + const patchId = getPatchId(blockPos, this.patchDimensions) + const patchKey = serializePatchId(patchId) + // look for patch in cache + const patch = this.patchLookup[patchKey] + return patch + } +} + +/** + * 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 + */ +export class GroundCache extends PatchesContainer { + static singleton: GroundCache + + static get instance() { + this.singleton = this.singleton || new GroundCache() + return this.singleton + } + get empty() { const emptyPatches = this.patches.filter(patch => patch.isEmpty) return emptyPatches } - getOverlappingPatches(inputBounds: Box2) { - return this.patches.filter(patch => patch.isOverlapping(inputBounds)) + async loadEmpty(on_the_fly = true) { + // const patchRequests = WorldComputeProxy.instance.iterPatchCompute(batch) + if (on_the_fly) { + // progressive loading + const patches = this.empty + async function* asyncGenerator() { + for (const patch of patches) { + await patch.fillGroundData() + yield patch + } + } + return asyncGenerator() + } else { + // all at once + return await Promise.all( + this.empty.map(async patch => { + await patch.fillGroundData() + return patch + }), + ) + } } - findPatch(blockPos: Vector2) { - const res = this.patches.find(patch => patch.containsPoint(blockPos)) - return res + /** + * Query block from cache + * @param blockPos + * @returns + */ + 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 + } + return block || pendingReq } -} -export class GroundMap extends PatchesContainer { - bounds: Box2 = new Box2() - patchDimensions + rebuildPatchIndex(cacheBounds: Box2) { + const patchKeys = getPatchIds(cacheBounds, this.patchDimensions).map(patchId => + serializePatchId(patchId), + ) + const foundOrMissing = patchKeys.map(key => this.patchLookup[key] || key) + const changesCount = foundOrMissing.filter( + item => typeof item === 'string', + ).length + if (changesCount > 0) { + const patchLookup: Record = {} + foundOrMissing + .map(item => + item instanceof GroundPatch ? item : new GroundPatch(item), + ) + .forEach(patch => (patchLookup[patch.key] = patch)) + this.patchLookup = patchLookup + } + return changesCount + } - constructor() { - super() - this.patchDimensions = WorldConf.regularPatchDimensions + precacheAroundPos(blockPos: Vector2, precacheRadius = 0) { + const center = blockPos.clone().floor() + const precache_dims = new Vector2( + precacheRadius, + precacheRadius, + ).multiplyScalar(2) + const precache_box = new Box2().setFromCenterAndSize( + center, + precache_dims, + ) + GroundCache.instance.rebuildPatchIndex(precache_box) + return GroundCache.instance.loadEmpty(false) } +} + +export class GroundMap extends GroundCache { + mapBounds: Box2 = new Box2() // adjustBounds(bounds: Box2) { // this.bounds = bounds @@ -46,9 +146,10 @@ export class GroundMap extends PatchesContainer { // this.loadEmpty() // } - rebuildPatchIndex(bounds: Box2) { - this.bounds = bounds - const patchKeys = getPatchIds(bounds, this.patchDimensions).map(patchId => + + override rebuildPatchIndex(mapBounds: Box2) { + this.mapBounds = mapBounds + const patchKeys = getPatchIds(mapBounds, this.patchDimensions).map(patchId => serializePatchId(patchId), ) const foundOrMissing = patchKeys.map(key => this.patchLookup[key] || key) @@ -67,29 +168,6 @@ export class GroundMap extends PatchesContainer { return changesCount } - async loadEmpty(on_the_fly = true) { - // const patchRequests = WorldComputeProxy.instance.iterPatchCompute(batch) - if (on_the_fly) { - // progressive loading - const patches = this.empty - async function* asyncGenerator() { - for (const patch of patches) { - await patch.fillGroundData() - yield patch - } - } - return asyncGenerator() - } else { - // all at once - return await Promise.all( - this.empty.map(async patch => { - await patch.fillGroundData() - return patch - }), - ) - } - } - // getBlock(blockPos: Vector3) { // return this.findPatch(blockPos)?.getBlock(blockPos, false) // } diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 7a151b0..cb8bd05 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -2,14 +2,10 @@ import alea from 'alea' import { Box2, Vector2 } from 'three' import { ProcLayer } from '../procgen/ProcLayer' -import { BlueNoisePattern } from '../procgen/BlueNoisePattern' -import { EntityData } from '../common/types' -import { WorldConf } from '../index' +import { BlueNoisePattern, DistributionParams } from '../procgen/BlueNoisePattern' import { getPatchIds } from '../common/utils' import { ItemType } from '../misc/ItemsInventory' - -// import { SurfaceNeighbour } from '../common/types' -// import { getAdjacent2dCoords } from '../common/utils' +import { WorldConf } from '../misc/WorldConfig' const probabilityThreshold = Math.pow(2, 8) const bmin = new Vector2(0, 0) @@ -18,16 +14,10 @@ const bmax = new Vector2( WorldConf.defaultDistMapPeriod, ) const distMapDefaultBox = new Box2(bmin, bmax) -const distMapDefaults = { - aleaSeed: 'treeMap', - minDistance: 8, - maxDistance: 100, - tries: 20, -} /** - * Approximated random distribution using infinite map made from patch repetition - * independant and deterministic + * Pseudo infinite random distribution from patch repetition + * with independant and deterministic behavior */ export class PseudoDistributionMap { patchDimensions: Vector2 @@ -36,7 +26,7 @@ export class PseudoDistributionMap { constructor( bounds: Box2 = distMapDefaultBox, - distParams: any = distMapDefaults, + distParams: DistributionParams = DistributionProfiles[DistributionProfile.MEDIUM], ) { this.patchDimensions = bounds.getSize(new Vector2()) this.repeatedPattern = new BlueNoisePattern(bounds, distParams) @@ -91,11 +81,10 @@ export class PseudoDistributionMap { return spawnLocations } - querySpawnedItems(queryBoxOrLoc: Vector2 | Box2, itemDims: Vector2, spawnableItems: ItemType[], spawnProbabilityEval = this.spawnProbabilityEval) { - const spawnedItems: Record = {} + getSpawnedItem(itemPos: Vector2, spawnableItems: ItemType[], spawnProbabilityEval = this.spawnProbabilityEval) { + // const spawnedItems: Record = {} const itemsCount = spawnableItems.length - const spawnablePlaces = this.querySpawnLocations(queryBoxOrLoc, itemDims) - spawnablePlaces.forEach(itemPos => { + // spawnablePlaces.forEach(itemPos => { const itemId = itemPos.x + ':' + itemPos.y const prng = alea(itemId) const rand = prng() @@ -103,14 +92,15 @@ export class PseudoDistributionMap { 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) - } + // if (itemKey !== undefined) { + // spawnedItems[itemKey] = spawnedItems[itemKey] || []; + // (spawnedItems[itemKey] as Vector2[]).push(itemPos) + // } + return itemKey } - }) + // }) - return spawnedItems + // return spawnedItems } // /** @@ -123,13 +113,40 @@ 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 */ export class OverlappingEntitiesMap { // extends RandomDistributionMap { // entities stored per biome - static biomeMapsLookup: Record = {} + // static biomeMapsLookup: Record = {} // getAdjacentEntities() { // const adjacentEntities = [] // const adjacentKeys = Object.values(SurfaceNeighbour) diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 1e42e29..caa6a1b 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -12,6 +12,7 @@ import { import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' import { BlockData, BlockMode } from '../datacontainers/GroundPatch' +import { ItemType } from '../misc/ItemsInventory' export enum BlockCategory { FLAT = 0, @@ -40,9 +41,9 @@ export type BoardInput = BoardInputParams & { center: Vector3 } // map block type to board block type const blockTypeCategoryMapper = (blockType: BlockType) => { switch (blockType) { - case BlockType.TREE_TRUNK: + case BlockType.TRUNK: return BlockCategory.OBSTACLE - case BlockType.BOARD_HOLE: + case BlockType.HOLE: return BlockCategory.HOLE default: return BlockCategory.FLAT @@ -146,25 +147,34 @@ export class BoardContainer extends GroundPatch { } async retrieveAndTrimTrees() { - const trees = await WorldComputeProxy.instance.queryEntities(this.bounds) - const trunks = trees - .map(entity => { - const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = this.getBlock(entityCenter) - entityCenter.y = entity.bbox.min.y - return entityCenterBlock - }) - .filter( - trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos), - ) as PatchBlock[] + // request all entities belonging to the board + 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 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) - trunks.forEach(trunkBlock => { - trunkBlock.data.type = BlockType.TREE_TRUNK - trunkBlock.data.mode = BlockMode.DEFAULT - trunkBlock.data.level += 1 - this.setBlock(trunkBlock.pos, trunkBlock.data) - }) - return trunks.map(({ pos, data }) => ({ pos, data }) as Block) + const boardBlock = this.getBlock(itemPos) + if (boardBlock) { + boardBlock.pos.y += 1 + boardBlock.data.level += 1 + boardBlock.data.type = BlockType.TRUNK + boardBlock.data.mode = BlockMode.DEFAULT + this.setBlock(boardBlock.pos, boardBlock.data) + boardItems.push(boardBlock.pos) + } + } + } + const boardItemsBlocks = boardItems.map(pos => ({ pos, type: 10 })) + return boardItemsBlocks } // perform local query @@ -196,7 +206,7 @@ export class BoardContainer extends GroundPatch { } digGroundHole(holeBlock: Block) { - holeBlock.data.type = BlockType.BOARD_HOLE + holeBlock.data.type = BlockType.HOLE holeBlock.data.level -= 1 // dig hole in the ground holeBlock.data.mode = BlockMode.DEFAULT this.setBlock(holeBlock.pos, holeBlock.data) diff --git a/src/index.ts b/src/index.ts index 53c9adf..b173bc2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,13 @@ - -export { Biome, BlockType } from './procgen/Biome' +export { Biome, BiomeType, BlockType } from './procgen/Biome' export { WorldConf } from './misc/WorldConfig' export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' +export { PseudoDistributionMap, DistributionProfile } from './datacontainers/RandomDistributionMap' export { BoardContainer } from './feats/BoardContainer' -export { EntityType } from './common/types' export { BlockMode, GroundPatch } from './datacontainers/GroundPatch' // export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundMap' -export { GroundMap } from './datacontainers/GroundMap' +export { GroundCache, GroundMap } from './datacontainers/GroundCache' export { ChunkFactory } from './tools/ChunkFactory' export { WorldComputeProxy } from './api/WorldComputeProxy' export { PatchContainer } from './datacontainers/PatchContainer' @@ -18,4 +16,4 @@ export { SchematicLoader } from './tools/SchematicLoader' export { ProceduralItemGenerator, ProcItemType, ProcItemCategory } from './tools/ProceduralGenerators' export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' -// export * as ProceduralGenerators from './tools/ProceduralGenerators' +// export * as ProceduralGenerators from './tools/ProceduralGenerators' \ No newline at end of file diff --git a/src/misc/ItemsInventory.ts b/src/misc/ItemsInventory.ts index d173572..9e4bebd 100644 --- a/src/misc/ItemsInventory.ts +++ b/src/misc/ItemsInventory.ts @@ -1,3 +1,4 @@ +import { Box3, Vector3 } from "three" import { ChunkContainer } from "../datacontainers/ChunkContainer" import { ProceduralItemGenerator, ProcItemConf } from "../tools/ProceduralGenerators" import { SchematicLoader } from "../tools/SchematicLoader" @@ -47,7 +48,25 @@ export class ItemsInventory { return chunk } - static async getItem(itemId: string) { + 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 + } } \ No newline at end of file diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 2248570..5bbadd3 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -6,27 +6,17 @@ import { MappingRangeSorter } from '../common/utils' import * as Utils from '../common/utils' import { ProcLayer } from './ProcLayer' -import { BiomeConfigs, BiomeConfKey, NoiseLevelConf } from '../common/types' +import { BiomeConfigs, BiomeLandscapeElement, BiomeLandscapeKey } from '../common/types' import { smoothstep } from 'three/src/math/MathUtils' +// reserved native block types export enum BlockType { NONE, - WATER, - ICE, - TREE_TRUNK, - TREE_FOLIAGE, - TREE_FOLIAGE_2, - SAND, - GRASS, - MUD, - ROCK, - SNOW, - BOARD_HOLE, - DBG_LIGHT, - DBG_DARK, - DBG_PURPLE, - DBG_ORANGE, - DBG_GREEN, + TRUNK, + FOLIAGE_LIGHT, + FOLIAGE_DARK, + HOLE, + LAST_PLACEHOLDER } enum Level { @@ -67,8 +57,6 @@ export enum BiomeType { } type Contribution = Record -type HeatContributions = Record -type RainContributions = Record const translateContribution = (contribution: Contribution, keyMapping: Record) => { const mappedContribution: Record = {} as Record @@ -132,7 +120,7 @@ export class Biome { seaLevel: 0, } - indexedConf = new Map + indexedConf = new Map constructor(biomeConf?: BiomeConfigs) { this.heatmap = new ProcLayer('heatmap') @@ -154,7 +142,7 @@ export class Biome { return Biome.singleton } - getConfIndex(confKey: BiomeConfKey) { + getConfIndex(confKey: BiomeLandscapeKey) { const confKeys = [...this.indexedConf.keys()]; // Spread keys into an array const confIndex = confKeys.indexOf(confKey); // Find the index of 'key2' return confIndex @@ -185,27 +173,27 @@ export class Biome { // 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.low = 1 - interp; contributions.mid = interp; } // MID - else if (value < steps.midToHigh) { - contributions.mid = 1; - } + else if (value < steps.midToHigh) { + 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; - } + } // HIGH else { - contributions.high = 1; + contributions.high = 1; } // if (value < 0.5) { @@ -257,9 +245,15 @@ export class Biome { } parseBiomesConfig(biomeConfigs: BiomeConfigs) { - Object.entries(biomeConfigs).forEach(([biomeType, biomeConf]) => { - // complete missing data - Object.entries(biomeConf).forEach(([confId, confData]) => confData.key = biomeType + '_' + confId) + // 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 + } + const configItems = Object.values(biomeConf) const mappingRanges = LinkedList.fromArrayAfterSorting( configItems, @@ -271,13 +265,14 @@ export class Biome { for (const conf of confIter) { this.indexedConf.set(conf.data.key, conf) } - }) + } + // }) } - noiseLevelTransition = ( + landscapeTransition = ( groundPos: Vector2, baseHeight: number, - blockMapping: NoiseLevelConf, + blockMapping: BiomeLandscapeElement, ) => { const period = 0.005 * Math.pow(2, 2) const mapCoords = groundPos.clone().multiplyScalar(period) diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index e1b0a0c..baba269 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -2,15 +2,24 @@ import alea from 'alea' import PoissonDiskSampling from 'poisson-disk-sampling' import { Box2, Vector2 } from 'three' +export type DistributionParams = { + minDistance: number + maxDistance?: number + tries?: number + distanceFunction?: ((point: any) => number) + bias?: number + aleaSeed?: string +} + /** * Self repeating seamless pattern */ export class BlueNoisePattern { bbox: Box2 - params + params: DistributionParams elements: Vector2[] = [] - constructor(bbox: Box2, distParams: any) { + constructor(bbox: Box2, distParams: DistributionParams) { this.bbox = bbox this.params = distParams this.populate() diff --git a/src/third-party/nbt_custom.ts b/src/third-party/nbt_custom.ts index 4d3d3b8..f4fc6b2 100644 --- a/src/third-party/nbt_custom.ts +++ b/src/third-party/nbt_custom.ts @@ -154,7 +154,7 @@ export class NBTReader { const slice = sliceUint8Array(this.arrayView, this.offset, this.offset + length); this.offset += length; - return decodeUTF8(slice); + return decodeUTF8(slice as any); }, [DataType.LIST]: () => { const type = this.read(DataType.BYTE) as DataType; @@ -205,7 +205,7 @@ export class NBTReader { read(dataType: DataType) { const dataSize = DataSizeMapping[dataType] || 0 const callee = 'get' + DataTypeMapping[dataType] - var val = dataType !== DataType.END ? this.dataView[callee](this.offset) : ''; + var val = dataType !== DataType.END ? (this.dataView as any)[callee](this.offset) : ''; this.offset += dataSize; return val; } @@ -271,36 +271,12 @@ export class NBTReader { * console.log(result.name); * console.log(result.value.foo); * }); */ - static parse(data, callback) { + static parse(data: any, callback: any) { if (!hasGzipHeader(data)) { callback(null, NBTReader.parseUncompressed(data)); - } else if (!zlib) { - callback(new Error('NBT archive is compressed but zlib is not ' + - 'available'), null); } else { - /* zlib.gunzip take a Buffer, at least in Node, so try to convert - if possible. */ - var buffer; - if (data.length) { - buffer = data; - } - // else if (typeof Buffer !== 'undefined') { - // buffer = new Buffer(data); - // } - else { - /* In the browser? Unknown zlib library. Let's settle for - Uint8Array and see what happens. */ - buffer = new Uint8Array(data); - } - - zlib.gunzip(buffer, function (error, uncompressed) { - if (error) { - callback(error, null); - } else { - callback(null, NBTReader.parseUncompressed(uncompressed)); - } - }); + callback(new Error('NBT compressed archive support is not implemented '), null); } }; diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 6c35d63..4b39d94 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,4 +1,4 @@ -import { Box3, MathUtils, Vector3 } from 'three' +import { MathUtils, Vector3 } from 'three' import { PatchId } from '../common/types' import { @@ -8,8 +8,7 @@ import { serializeChunkId, } from '../common/utils' import { ChunkContainer } from '../datacontainers/ChunkContainer' -import { BlockMode, BlockType, GroundPatch, ItemsInventory, WorldConf } from '../index' -import { ItemType } from '../misc/ItemsInventory' +import { BlockMode, BlockType, GroundPatch, WorldConf } from '../index' // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -54,17 +53,19 @@ export class ChunkFactory { /** * Assembles pieces together: ground, world objects */ - async chunkifyPatch(groundLayer: GroundPatch, overgroundItems: Record) { + chunkifyPatch(groundLayer: GroundPatch, chunkItems: ChunkContainer[]) { const patchChunkIds = groundLayer.id ? ChunkFactory.default.genChunksIdsFromPatchId(groundLayer.id) : [] // const worldChunksStubs = patchChunkIds.map(async chunkId => { - const worldChunksStubs = await Promise.all(patchChunkIds.map(async chunkId => { + const worldChunksStubs = patchChunkIds.map(chunkId => { const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) const worldChunk = new ChunkContainer(chunkBox) // this.mergePatchLayersToChunk(worldChunk, groundLayer, overgroundItems) - // merge items first so they don't override ground - await this.mergeOvergroundItems(worldChunk, overgroundItems) + // merge chunk items first so they don't override ground + for (const chunkItem of chunkItems) { + ChunkContainer.copySourceToTarget(chunkItem, worldChunk) + } // merge ground layer after, overriding items blocks overlapping with ground this.mergeGroundLayer(worldChunk, groundLayer) const worldChunkStub = { @@ -72,7 +73,7 @@ export class ChunkFactory { data: worldChunk.rawData, } return worldChunkStub - })) + }) return worldChunksStubs } @@ -103,27 +104,4 @@ export class ChunkFactory { } } } - - async mergeOvergroundItems(worldChunk: ChunkContainer, overgroundItems: Record) { - // Object.entries(overgroundItems).forEach(([itemType, spawnPlaces]) => { - const entries = Object.entries(overgroundItems) - for await (const [itemType, spawnPlaces] of entries) { - const itemChunk = await ItemsInventory.getItem(itemType) - if (itemChunk) { - spawnPlaces.forEach(spawnLoc => { - const dims = itemChunk.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(spawnLoc, dims) - entityBounds.min.y = spawnLoc.y - entityBounds.max.y = spawnLoc.y + dims.y - entityBounds.min.floor() - entityBounds.max.floor() - const entityChunk = new ChunkContainer(entityBounds, 0) - entityChunk.rawData.set(itemChunk.rawData) - ChunkContainer.copySourceToTarget(entityChunk, worldChunk) - }) - } - }//) - } } diff --git a/src/tools/ProceduralGenerators.ts b/src/tools/ProceduralGenerators.ts index 0ec66e9..b8c0954 100644 --- a/src/tools/ProceduralGenerators.ts +++ b/src/tools/ProceduralGenerators.ts @@ -24,13 +24,13 @@ type TreeGenerator = (xzProj: number, y: number, range: number) => BlockType const AppleTreeGen = (xzProj: number, y: number, range: number): BlockType => { const dist = Math.sqrt(Math.pow(xzProj, 2) + Math.pow(y, 2)) const isFoliage = dist <= range - return isFoliage ? BlockType.TREE_FOLIAGE : BlockType.NONE + return isFoliage ? BlockType.FOLIAGE_LIGHT : BlockType.NONE } const PineTreeGen = (xzProj: number, y: number, range: number): BlockType => { const dist = xzProj // xzProj*(y+radius) const isFoliage = dist <= range * (1 - (0.35 * (y + range)) / range) - return isFoliage ? BlockType.TREE_FOLIAGE_2 : BlockType.NONE + return isFoliage ? BlockType.FOLIAGE_DARK : BlockType.NONE } type ProceduralGenerator = TreeGenerator @@ -49,6 +49,7 @@ export class ProceduralItemGenerator { const { treeType, treeSize, treeRadius } = itemParams return this.voxelizeTree(treeType, treeSize, treeRadius) } + return } static voxelizeTree(treeType: ProcItemType, treeSize: number, treeRadius: number) { @@ -57,28 +58,27 @@ export class ProceduralItemGenerator { 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()) - const { min, max } = treeBounds let index = 0 const chunkIter = treeChunk.iterateContent() for (const chunkBlock of chunkIter) { const { x, y, z } = chunkBlock.localPos const xzProj = new Vector2(x, z).sub(asVect2(entityPos)) if (xzProj.length() > 0) { - if (y < min.y + treeSize) { + if (y < treeBounds.min.y + treeSize) { // empty space around trunk between ground and trunk top treeChunk.rawData[index++] = chunkDataEncoder(BlockType.NONE) } else { // tree foliage const blockType = treeGenerator( xzProj.length(), - y - (min.y + treeSize + treeRadius), + y - (treeBounds.min.y + treeSize + treeRadius), treeRadius, ) treeChunk.rawData[index++] = chunkDataEncoder(blockType) } } else { // tree trunk - treeChunk.rawData[index++] = chunkDataEncoder(BlockType.TREE_TRUNK) + treeChunk.rawData[index++] = chunkDataEncoder(BlockType.TRUNK) } } return treeChunk diff --git a/src/tools/SchematicLoader.ts b/src/tools/SchematicLoader.ts index 2d71504..1088326 100644 --- a/src/tools/SchematicLoader.ts +++ b/src/tools/SchematicLoader.ts @@ -12,7 +12,6 @@ export class SchematicLoader { static async load(path: string) { // const schem = await Schematic.read(Buffer.from(schemData), '1.16.4') const res = await fetch(path); - console.log(res); const blob = await res.blob(); const rawData = await new Promise((resolve) => { const reader = new FileReader(); @@ -25,9 +24,9 @@ export class SchematicLoader { return rawData } - static async parse(rawData) { - return new Promise((resolve, reject) => { - NBTReader.parse(rawData, function (error, data) { + static async parse(rawData: any) { + return new Promise((resolve) => { + NBTReader.parse(rawData, function (error: any, data: unknown) { if (error) { throw error; } resolve(data); }); @@ -44,7 +43,7 @@ export class SchematicLoader { const rawData = await SchematicLoader.load(fileUrl) const parsedSchematic = await SchematicLoader.parse(rawData) - const schemBlocks = SchematicLoader.getBlocks(parsedSchematic) + 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) @@ -71,7 +70,7 @@ export class SchematicLoader { return chunkContainer } - static getBlocks(schemData) { + static getBlocks(schemData: any) { // Get dimensions of the schematic const width = schemData.value.Width.value; const height = schemData.value.Height.value; @@ -83,7 +82,7 @@ export class SchematicLoader { // Create a new 3d array let skippedBlocks = []; - let blocks = []; + let blocks: any = []; for (let y = 0; y < height; y++) { blocks[y] = []; for (let x = 0; x < width; x++) { @@ -105,10 +104,10 @@ export class SchematicLoader { return blocks; } - static getBlockData(palette, blockId) { + 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.value === blockId) { + if ((value as any).value === blockId) { // If the key contains a closing bracket, return only everything before the bracket if (key.includes("[")) { return { From dbb1049799256e477e0798e1d9a8377aab99f492 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 22 Oct 2024 05:14:12 +0000 Subject: [PATCH 10/13] 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 */ From 077b0551299a8772ba5ad2cc0c4077ed0a3bf2e1 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 22 Oct 2024 06:31:22 +0000 Subject: [PATCH 11/13] fix: package-lock --- package-lock.json | 487 +++++++++++++++++++++------------------------- 1 file changed, 219 insertions(+), 268 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78ad4b8..9838517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "@aresrpg/aresrpg-world", "version": "1.4.5", "dependencies": { + "@types/pako": "^2.0.3", "alea": "^1.0.1", + "pako": "^2.1.0", "poisson-disk-sampling": "^2.3.1", "simplex-noise": "^4.0.3" }, @@ -33,12 +35,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" }, "engines": { @@ -46,21 +48,21 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -156,9 +158,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -210,22 +212,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -276,9 +278,9 @@ "dev": true }, "node_modules/@lezer/common": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", - "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", "dev": true }, "node_modules/@lezer/lr": { @@ -1182,14 +1184,14 @@ } }, "node_modules/@swc/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.3.tgz", - "integrity": "sha512-HHAlbXjWI6Kl9JmmUW1LSygT1YbblXgj2UvvDzMkTBPRzYMhW6xchxdO8HbtMPtFYRt/EQq9u1z7j4ttRSrFsA==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.39.tgz", + "integrity": "sha512-jns6VFeOT49uoTKLWIEfiQqJAlyqldNAt80kAr8f7a5YjX0zgnG3RBiLMpksx4Ka4SlK4O6TJ/lumIM3Trp82g==", "dev": true, "hasInstallScript": true, "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.12" + "@swc/types": "^0.1.13" }, "engines": { "node": ">=10" @@ -1199,16 +1201,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.7.3", - "@swc/core-darwin-x64": "1.7.3", - "@swc/core-linux-arm-gnueabihf": "1.7.3", - "@swc/core-linux-arm64-gnu": "1.7.3", - "@swc/core-linux-arm64-musl": "1.7.3", - "@swc/core-linux-x64-gnu": "1.7.3", - "@swc/core-linux-x64-musl": "1.7.3", - "@swc/core-win32-arm64-msvc": "1.7.3", - "@swc/core-win32-ia32-msvc": "1.7.3", - "@swc/core-win32-x64-msvc": "1.7.3" + "@swc/core-darwin-arm64": "1.7.39", + "@swc/core-darwin-x64": "1.7.39", + "@swc/core-linux-arm-gnueabihf": "1.7.39", + "@swc/core-linux-arm64-gnu": "1.7.39", + "@swc/core-linux-arm64-musl": "1.7.39", + "@swc/core-linux-x64-gnu": "1.7.39", + "@swc/core-linux-x64-musl": "1.7.39", + "@swc/core-win32-arm64-msvc": "1.7.39", + "@swc/core-win32-ia32-msvc": "1.7.39", + "@swc/core-win32-x64-msvc": "1.7.39" }, "peerDependencies": { "@swc/helpers": "*" @@ -1220,9 +1222,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.3.tgz", - "integrity": "sha512-CTkHa6MJdov9t41vuV2kmQIMu+Q19LrEHGIR/UiJYH06SC/sOu35ZZH8DyfLp9ZoaCn21gwgWd61ixOGQlwzTw==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.39.tgz", + "integrity": "sha512-o2nbEL6scMBMCTvY9OnbyVXtepLuNbdblV9oNJEFia5v5eGj9WMrnRQiylH3Wp/G2NYkW7V1/ZVW+kfvIeYe9A==", "cpu": [ "arm64" ], @@ -1236,9 +1238,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.3.tgz", - "integrity": "sha512-mun623y6rCoZ2EFIYfIRqXYRFufJOopoYSJcxYhZUrfTpAvQ1zLngjQpWCUU1krggXR2U0PQj+ls0DfXUTraNg==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.39.tgz", + "integrity": "sha512-qMlv3XPgtPi/Fe11VhiPDHSLiYYk2dFYl747oGsHZPq+6tIdDQjIhijXPcsUHIXYDyG7lNpODPL8cP/X1sc9MA==", "cpu": [ "x64" ], @@ -1252,9 +1254,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.3.tgz", - "integrity": "sha512-4Jz4UcIcvZNMp9qoHbBx35bo3rjt8hpYLPqnR4FFq6gkAsJIMFC56UhRZwdEQoDuYiOFMBnnrsg31Fyo6YQypA==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.39.tgz", + "integrity": "sha512-NP+JIkBs1ZKnpa3Lk2W1kBJMwHfNOxCUJXuTa2ckjFsuZ8OUu2gwdeLFkTHbR43dxGwH5UzSmuGocXeMowra/Q==", "cpu": [ "arm" ], @@ -1268,9 +1270,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.3.tgz", - "integrity": "sha512-p+U/M/oqV7HC4erQ5TVWHhJU1984QD+wQBPxslAYq751bOQGm0R/mXK42GjugqjnR6yYrAiwKKbpq4iWVXNePA==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.39.tgz", + "integrity": "sha512-cPc+/HehyHyHcvAsk3ML/9wYcpWVIWax3YBaA+ScecJpSE04l/oBHPfdqKUPslqZ+Gcw0OWnIBGJT/fBZW2ayw==", "cpu": [ "arm64" ], @@ -1284,9 +1286,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.3.tgz", - "integrity": "sha512-s6VzyaJwaRGTi2mz2h6Ywxfmgpkc69IxhuMzl+sl34plH0V0RgnZDm14HoCGIKIzRk4+a2EcBV1ZLAfWmPACQg==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.39.tgz", + "integrity": "sha512-8RxgBC6ubFem66bk9XJ0vclu3exJ6eD7x7CwDhp5AD/tulZslTYXM7oNPjEtje3xxabXuj/bEUMNvHZhQRFdqA==", "cpu": [ "arm64" ], @@ -1300,9 +1302,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.3.tgz", - "integrity": "sha512-IrFY48C356Z2dU2pjYg080yvMXzmSV3Lmm/Wna4cfcB1nkVLjWsuYwwRAk9CY7E19c+q8N1sMNggubAUDYoX2g==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.39.tgz", + "integrity": "sha512-3gtCPEJuXLQEolo9xsXtuPDocmXQx12vewEyFFSMSjOfakuPOBmOQMa0sVL8Wwius8C1eZVeD1fgk0omMqeC+Q==", "cpu": [ "x64" ], @@ -1316,9 +1318,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.3.tgz", - "integrity": "sha512-qoLgxBlBnnyUEDu5vmRQqX90h9jldU1JXI96e6eh2d1gJyKRA0oSK7xXmTzorv1fGHiHulv9qiJOUG+g6uzJWg==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.39.tgz", + "integrity": "sha512-mg39pW5x/eqqpZDdtjZJxrUvQNSvJF4O8wCl37fbuFUqOtXs4TxsjZ0aolt876HXxxhsQl7rS+N4KioEMSgTZw==", "cpu": [ "x64" ], @@ -1332,9 +1334,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.3.tgz", - "integrity": "sha512-OAd7jVVJ7nb0Ev80VAa1aeK+FldPeC4eZ35H4Qn6EICzIz0iqJo2T33qLKkSZiZEBKSoF4KcwrqYfkjLOp5qWg==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.39.tgz", + "integrity": "sha512-NZwuS0mNJowH3e9bMttr7B1fB8bW5svW/yyySigv9qmV5VcQRNz1kMlCvrCLYRsa93JnARuiaBI6FazSeG8mpA==", "cpu": [ "arm64" ], @@ -1348,9 +1350,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.3.tgz", - "integrity": "sha512-31+Le1NyfSnILFV9+AhxfFOG0DK0272MNhbIlbcv4w/iqpjkhaOnNQnLsYJD1Ow7lTX1MtIZzTjOhRlzSviRWg==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.39.tgz", + "integrity": "sha512-qFmvv5UExbJPXhhvCVDBnjK5Duqxr048dlVB6ZCgGzbRxuarOlawCzzLK4N172230pzlAWGLgn9CWl3+N6zfHA==", "cpu": [ "ia32" ], @@ -1364,9 +1366,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.3.tgz", - "integrity": "sha512-jVQPbYrwcuueI4QB0fHC29SVrkFOBcfIspYDlgSoHnEz6tmLMqUy+txZUypY/ZH/KaK0HEY74JkzgbRC1S6LFQ==", + "version": "1.7.39", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.39.tgz", + "integrity": "sha512-o+5IMqgOtj9+BEOp16atTfBgCogVak9svhBpwsbcJQp67bQbxGYhAPPDW/hZ2rpSSF7UdzbY9wudoX9G4trcuQ==", "cpu": [ "x64" ], @@ -1386,9 +1388,9 @@ "dev": true }, "node_modules/@swc/types": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", - "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", + "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", "dev": true, "dependencies": { "@swc/counter": "^0.1.3" @@ -1406,6 +1408,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1425,11 +1432,10 @@ "dev": true }, "node_modules/@types/three": { - "version": "0.167.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.1.tgz", - "integrity": "sha512-OCd2Uv/8/4TbmSaIRFawrCOnDMLdpaa+QGJdhlUBmdfbHjLY8k6uFc0tde2/UvcaHQ6NtLl28onj/vJfofV+Tg==", + "version": "0.167.2", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.2.tgz", + "integrity": "sha512-onxnIUNYpXcZJ5DTiIsxfnr4F9kAWkkxAUWx5yqzz/u0a4IygCLCjMuOl2DEeCxyJdJ2nOJZvKpu48sBMqfmkQ==", "dev": true, - "license": "MIT", "dependencies": { "@tweenjs/tween.js": "~23.1.2", "@types/stats.js": "*", @@ -1439,23 +1445,22 @@ } }, "node_modules/@types/webxr": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz", - "integrity": "sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.20.tgz", + "integrity": "sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0.tgz", - "integrity": "sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", + "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.0.0", - "@typescript-eslint/type-utils": "8.0.0", - "@typescript-eslint/utils": "8.0.0", - "@typescript-eslint/visitor-keys": "8.0.0", + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/type-utils": "8.11.0", + "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1479,16 +1484,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0.tgz", - "integrity": "sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", + "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.0.0", - "@typescript-eslint/types": "8.0.0", - "@typescript-eslint/typescript-estree": "8.0.0", - "@typescript-eslint/visitor-keys": "8.0.0", + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4" }, "engines": { @@ -1508,14 +1512,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0.tgz", - "integrity": "sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", + "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0", - "@typescript-eslint/visitor-keys": "8.0.0" + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1526,14 +1529,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0.tgz", - "integrity": "sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz", + "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.0.0", - "@typescript-eslint/utils": "8.0.0", + "@typescript-eslint/typescript-estree": "8.11.0", + "@typescript-eslint/utils": "8.11.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1551,11 +1553,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0.tgz", - "integrity": "sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1565,16 +1566,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0.tgz", - "integrity": "sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.0.0", - "@typescript-eslint/visitor-keys": "8.0.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -1594,16 +1594,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0.tgz", - "integrity": "sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", + "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.0.0", - "@typescript-eslint/types": "8.0.0", - "@typescript-eslint/typescript-estree": "8.0.0" + "@typescript-eslint/scope-manager": "8.11.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/typescript-estree": "8.11.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1617,13 +1616,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0.tgz", - "integrity": "sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0", + "@typescript-eslint/types": "8.11.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1648,9 +1646,9 @@ "peer": true }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1770,16 +1768,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlastindex": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", @@ -1894,7 +1882,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1912,9 +1899,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -1932,10 +1919,10 @@ ], "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -1996,9 +1983,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001643", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", - "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", + "version": "1.0.30001669", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", + "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", "dev": true, "funding": [ { @@ -2215,12 +2202,12 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2283,19 +2270,6 @@ "node": ">=0.10" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2326,16 +2300,16 @@ "peer": true }, "node_modules/electron-to-chromium": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz", - "integrity": "sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ==", + "version": "1.5.42", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.42.tgz", + "integrity": "sha512-gIfKavKDw1mhvic9nbzA5lZw8QSHpdMwLwXc0cWidQz9B15pDoDdDH4boIatuFfeoCatb3a/NGL6CYRVFxGZ9g==", "dev": true, "peer": true }, "node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, "node_modules/environment": { @@ -2493,9 +2467,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "peer": true, "engines": { @@ -2515,17 +2489,17 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -2648,9 +2622,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -2996,7 +2970,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3013,7 +2986,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -3175,9 +3147,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, "engines": { "node": ">=18" @@ -3235,9 +3207,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", - "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", "dev": true, "peer": true, "dependencies": { @@ -3333,27 +3305,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3468,7 +3419,6 @@ "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "chalk": "^4.0.0", "ci-info": "^2.0.0", @@ -3494,9 +3444,9 @@ } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -3637,9 +3587,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -4017,9 +3967,9 @@ } }, "node_modules/listr2": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", - "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, "dependencies": { "cli-truncate": "^4.0.0", @@ -4105,9 +4055,9 @@ } }, "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "engines": { "node": ">=12" @@ -4185,7 +4135,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -4197,9 +4146,9 @@ "dev": true }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3", @@ -4238,7 +4187,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4264,9 +4212,9 @@ "integrity": "sha512-xcsFo/jgtMuVaGePHod5TdSzxnRAQQ4wFpDmFuu34lHvx5sNMsioA84NW7iBWYZ10jHR/nyGaDkhunMJxqAzkw==" }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/msgpackr": { @@ -4539,9 +4487,9 @@ } }, "node_modules/ordered-binary": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", - "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", + "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", "dev": true }, "node_modules/p-limit": { @@ -4574,6 +4522,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4647,9 +4600,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -4728,7 +4681,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -4769,15 +4721,15 @@ ] }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -5146,9 +5098,9 @@ } }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "engines": { "node": ">=12" @@ -5297,9 +5249,9 @@ "dev": true }, "node_modules/three": { - "version": "0.167.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.167.0.tgz", - "integrity": "sha512-9Y1a66fpjqF3rhq7ivKTaKtjQLZ97Hj/lZ00DmZWaKHaQFH4uzYT1znwRDWQOcgMmCcOloQzo61gDmqO8l9xmA==", + "version": "0.169.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.169.0.tgz", + "integrity": "sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==", "peer": true }, "node_modules/to-regex-range": { @@ -5319,7 +5271,6 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=16" }, @@ -5449,9 +5400,9 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5477,9 +5428,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -5497,8 +5448,8 @@ ], "peer": true, "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -5617,9 +5568,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "engines": { "node": ">=12" From 0e98375ee8826037b084e61789a1ea062e612f24 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 24 Oct 2024 15:13:53 +0000 Subject: [PATCH 12/13] misc(biome): support numeric+string biome type + native block type placeholders --- src/api/world-compute.ts | 27 ++++++++++++++------------- src/common/utils.ts | 13 +++++++++---- src/procgen/Biome.ts | 22 +++++++++++++++++++--- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index fd489cf..8b3b42b 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -9,6 +9,7 @@ import { import { Biome, BiomeInfluence, BiomeType, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' import { + Block, BlockData, GroundBlock, LandscapesConf, @@ -58,20 +59,20 @@ 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) + // .map(k => parseInt(k) as BiomeType) + .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 => { - const boundPos = boundsPoints[key] as Vector2 - const biomeInfluence = Biome.instance.getBiomeInfluence(boundPos) - boundsInfluences[key] = biomeInfluence - // const block = computeGroundBlock(asVect3(pos), biomeInfluence) - return biomeInfluence - }) + ;[xMyM, xMyP, xPyM, xPyP].map(key => { + const boundPos = boundsPoints[key] as Vector2 + const biomeInfluence = Biome.instance.getBiomeInfluence(asVect3(boundPos)) + boundsInfluences[key] = biomeInfluence + // const block = computeGroundBlock(asVect3(pos), biomeInfluence) + return biomeInfluence + }) const allEquals = equals(boundsInfluences[xMyM], boundsInfluences[xPyM]) && equals(boundsInfluences[xMyM], boundsInfluences[xMyP]) && @@ -117,11 +118,10 @@ export const computeGroundBlock = ( biomeInfluence, ) let usedConf = nominalConf - // const pos = new Vector3(blockPos.x, level, blockPos.z) 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) @@ -169,7 +169,7 @@ export const computeBlocksBatch = async ( level: 0, type: BlockType.NONE, } - const block: GroundBlock = { + const block: Block = { pos: asVect3(pos), data, } @@ -187,6 +187,7 @@ export const computeBlocksBatch = async ( biomeBoundsInfluences, ) block.data = computeGroundBlock(block.pos, blockBiome) + // const {level, type } = // override with last block if specified if (params.includeEntitiesBlocks) { const lastBlockData = await queryLastBlockData(asVect2(block.pos)) diff --git a/src/common/utils.ts b/src/common/utils.ts index 7b19274..4f28608 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -13,6 +13,10 @@ import { LandscapesConf, } from './types' +const typesNumbering = (types: Record, offset = 0) => Object.keys(types).forEach( + (key, i) => (types[key] = offset + i), +) + // Clamp number between two values: const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max) @@ -361,10 +365,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 } @@ -477,6 +481,7 @@ const chunkBoxFromKey = (chunkKey: string, chunkDims: Vector3) => { } export { + typesNumbering, roundToDec, vectRoundToDec, smoothstep, diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index c93ff72..6810aaa 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -18,11 +18,18 @@ import { ProcLayer } from './ProcLayer' // reserved native block types export enum BlockType { NONE, + HOLE, + BEDROCK, + WATER, + ICE, MUD, TRUNK, + SAND, + GRASS, + ROCK, + SNOW, FOLIAGE_LIGHT, FOLIAGE_DARK, - HOLE, LAST_PLACEHOLDER, } @@ -63,6 +70,15 @@ export enum BiomeType { // Tropical = 'tropical', } +export const BiomeNumericType: Record = { + [BiomeType.Temperate]: 0, + [BiomeType.Artic]: 0, + [BiomeType.Desert]: 0 +} +Utils.typesNumbering(BiomeNumericType) +export const ReverseBiomeNumericType: Record = {} +Object.keys(BiomeNumericType).forEach((type, i) => ReverseBiomeNumericType[i] = type as BiomeType) + type Contribution = Record const translateContribution = ( @@ -166,10 +182,10 @@ export class Biome { getBiomeType(input: Vector3 | BiomeInfluence) { const biomeContribs = input instanceof Vector3 ? this.getBiomeInfluence(input) : input - const mainBiome = Object.entries(biomeContribs).sort( + const dominantBiome = Object.entries(biomeContribs).sort( (a, b) => b[1] - a[1], )[0]?.[0] as string - return mainBiome as BiomeType + return dominantBiome as BiomeType } calculateContributions(value: number) { From dde0fe0de89fd151b985393e1c4141e81b5f407d Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 24 Oct 2024 15:21:01 +0000 Subject: [PATCH 13/13] fix: lint, formatting --- src/api/world-compute.ts | 18 +++++++++--------- src/common/utils.ts | 13 ++++++------- src/procgen/Biome.ts | 6 ++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 8b3b42b..2ab8f2a 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -66,13 +66,13 @@ const getBiomeBoundsInfluences = (bounds: Box2) => { } const boundsPoints = getPatchBoundingPoints(bounds) const boundsInfluences = {} as PatchBoundingBiomes - ;[xMyM, xMyP, xPyM, xPyP].map(key => { - const boundPos = boundsPoints[key] as Vector2 - const biomeInfluence = Biome.instance.getBiomeInfluence(asVect3(boundPos)) - boundsInfluences[key] = biomeInfluence - // const block = computeGroundBlock(asVect3(pos), biomeInfluence) - return biomeInfluence - }) + ;[xMyM, xMyP, xPyM, xPyP].map(key => { + const boundPos = boundsPoints[key] as Vector2 + const biomeInfluence = Biome.instance.getBiomeInfluence(asVect3(boundPos)) + boundsInfluences[key] = biomeInfluence + // const block = computeGroundBlock(asVect3(pos), biomeInfluence) + return biomeInfluence + }) const allEquals = equals(boundsInfluences[xMyM], boundsInfluences[xPyM]) && equals(boundsInfluences[xMyM], boundsInfluences[xMyP]) && @@ -121,7 +121,7 @@ export const computeGroundBlock = ( if (nominalConf.next?.data) { const variation = Biome.instance.posRandomizer.eval( blockPos.clone().multiplyScalar(50), - ) + ) 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) @@ -187,7 +187,7 @@ export const computeBlocksBatch = async ( biomeBoundsInfluences, ) block.data = computeGroundBlock(block.pos, blockBiome) - // const {level, type } = + // const {level, type } = // override with last block if specified if (params.includeEntitiesBlocks) { const lastBlockData = await queryLastBlockData(asVect2(block.pos)) diff --git a/src/common/utils.ts b/src/common/utils.ts index 4f28608..ceadb60 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -13,9 +13,8 @@ import { LandscapesConf, } from './types' -const typesNumbering = (types: Record, offset = 0) => Object.keys(types).forEach( - (key, i) => (types[key] = offset + i), -) +const typesNumbering = (types: Record, offset = 0) => + Object.keys(types).forEach((key, i) => (types[key] = offset + i)) // Clamp number between two values: const clamp = (num: number, min: number, max: number) => @@ -365,10 +364,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/procgen/Biome.ts b/src/procgen/Biome.ts index 6810aaa..bb9e054 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -73,11 +73,13 @@ export enum BiomeType { export const BiomeNumericType: Record = { [BiomeType.Temperate]: 0, [BiomeType.Artic]: 0, - [BiomeType.Desert]: 0 + [BiomeType.Desert]: 0, } Utils.typesNumbering(BiomeNumericType) export const ReverseBiomeNumericType: Record = {} -Object.keys(BiomeNumericType).forEach((type, i) => ReverseBiomeNumericType[i] = type as BiomeType) +Object.keys(BiomeNumericType).forEach((type, i) => { + ReverseBiomeNumericType[i] = type as BiomeType +}) type Contribution = Record