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 {