From 8e608f71696ff6cd7be24c5d087e1873d6c7c562 Mon Sep 17 00:00:00 2001 From: etienne Date: Sat, 10 Aug 2024 17:08:13 +0000 Subject: [PATCH 01/45] feat: extends world utils + misc cache changes --- src/common/utils.ts | 39 +++++++++++++++++++- src/index.ts | 2 +- src/world/WorldCache.ts | 80 ++++++++++++++++++++--------------------- src/world/WorldPatch.ts | 10 ++++-- 4 files changed, 85 insertions(+), 46 deletions(-) diff --git a/src/common/utils.ts b/src/common/utils.ts index 628042e..6acd5fc 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,4 +1,4 @@ -import { Box3, Vector2, Vector3 } from 'three' +import { Box3, Vector2, Vector3, Vector3Like } from 'three' import { Adjacent2dPos, @@ -184,6 +184,41 @@ const bboxContainsPointXZ = (bbox: Box3, point: Vector3) => { ) } +const asVect3 = (vect2: Vector2, yVal = 0) => { + return new Vector3(vect2.x, yVal, vect2.y) +} + +const parseVect3Stub = (stub: Vector3Like) => { + let res + if (isVect3Stub(stub)) { + res = new Vector3(...Object.values(stub)) + } + return res +} + +const parseBox3Stub = (stub: Box3) => { + let res + if (isVect3Stub(stub.min) && isVect3Stub(stub.max)) { + const min = parseVect3Stub(stub.min) + const max = parseVect3Stub(stub.max) + res = new Box3(min, max) + } + return res +} + +const isVect3Stub = (stub: Vector3Like) => { + return ( + stub !== undefined && + stub.x !== undefined && + stub.y !== undefined && + stub.z !== undefined + ) +} + +const parseThreeStub = (stub: any) => { + return parseBox3Stub(stub) || parseVect3Stub(stub) || stub +} + export { roundToDec, clamp, @@ -196,4 +231,6 @@ export { getAllNeighbours3dCoords, bboxContainsPointXZ, getPatchPoints, + parseThreeStub, + asVect3, } diff --git a/src/index.ts b/src/index.ts index cd0a36a..36a45a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,6 @@ export { EntityType } from './common/types' export { WorldApi, WorldWorkerApi, WorldApiName } from './world/WorldApi' export { WorldCache } from './world/WorldCache' export { WorldCompute } from './world/WorldCompute' - +export * as WorldUtils from './common/utils' // export type { MappingConf, MappingData, MappingRanges } from "./common/types" // export { DevHacks } from './tools/DevHacks' diff --git a/src/world/WorldCache.ts b/src/world/WorldCache.ts index 2efe8ac..dc5ef23 100644 --- a/src/world/WorldCache.ts +++ b/src/world/WorldCache.ts @@ -19,7 +19,7 @@ export class WorldCache { static bbox = new Box3() // global cache extent static pendingRefresh = false static cacheCenter = new Vector2(0, 0) - static cachePowRadius = 3 + static cachePowRadius = 1 static cacheSize = BlocksPatch.patchSize * 5 // static worldApi = new WorldApi() @@ -50,60 +50,56 @@ export class WorldCache { return batchRes } - static async refresh( - center: Vector3, - // worldProxy: WorldProxy = PatchProcessing, - // asyncMode = false, - ) { - const { patchSize } = BlocksPatch - const { cachePowRadius } = this - const range = Math.pow(2, cachePowRadius) - const center_x = Math.floor(center.x / patchSize) - const center_z = Math.floor(center.z / patchSize) - const cacheCenter = new Vector2(center_x, center_z) - const cachePatchCount = Object.keys(this.patchLookupIndex).length - const batchContent: string[] = [] - if ( - !this.pendingRefresh && - (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) - ) { - this.pendingRefresh = true - this.cacheCenter = cacheCenter - - const existing: BlocksPatch[] = [] - for (let xmin = center_x - range; xmin < center_x + range; xmin += 1) { - for (let zmin = center_z - range; zmin < center_z + range; zmin += 1) { + static async refresh(bbox: Box3) { + const changes: any = { + batch: [], + count: 0, + } + if (!this.pendingRefresh) { + const range_min = BlocksPatch.asPatchCoords(bbox.min) + const range_max = BlocksPatch.asPatchCoords(bbox.max) + this.bbox = bbox + const backup = [] + for (let {x} = range_min; x < range_max.x + 1; x += 1) { + for (let {y} = range_min; y < range_max.y + 1; y += 1) { // const patchStart = new Vector2(xmin, zmin) - const patchIndexKey = 'patch_' + xmin + '_' + zmin + const patchIndexKey = 'patch_' + x + '_' + y // look for existing patch in current cache const patch = this.patchLookupIndex[patchIndexKey] // || new BlocksPatch(patchStart) //BlocksPatch.getPatch(patchBbox, true) as BlocksPatch if (!patch) { // patch = new BlocksPatch(patchStart) // add all patch needing to be filled up - batchContent.push(patchIndexKey) + changes.batch.push(patchIndexKey) } else { - existing.push(patch) + backup.push(patch) } } } - // const updated = existing.filter(patch => patch.state < PatchState.Finalised) - // const removedCount = Object.keys(WorldCache.patchLookupIndex).length - existing.length - WorldCache.patchLookupIndex = {} - existing.forEach( - patch => (WorldCache.patchLookupIndex[patch.key] = patch), - ) - - const batchIter = WorldCache.processBatchItems(batchContent) - for await (const patchStub of batchIter) { - const patch = BlocksPatch.fromStub(patchStub) - WorldCache.patchLookupIndex[patch.key] = patch - WorldCache.bbox.union(patch.bbox) + changes.count = + changes.batch.length + + Object.keys(WorldCache.patchLookupIndex).length - + backup.length + // update required + if (changes.count > 0) { + this.pendingRefresh = true + // const updated = existing.filter(patch => patch.state < PatchState.Finalised) + // const removedCount = Object.keys(WorldCache.patchLookupIndex).length - existing.length + WorldCache.patchLookupIndex = {} + backup.forEach( + patch => (WorldCache.patchLookupIndex[patch.key] = patch), + ) + + const batchIter = WorldCache.processBatchItems(changes.batch) + for await (const patchStub of batchIter) { + const patch = BlocksPatch.fromStub(patchStub) + WorldCache.patchLookupIndex[patch.key] = patch + // WorldCache.bbox.union(patch.bbox) + } + this.pendingRefresh = false } - this.pendingRefresh = false - return batchContent } - return batchContent + return changes } static getPatch(inputPoint: Vector2 | Vector3) { diff --git a/src/world/WorldPatch.ts b/src/world/WorldPatch.ts index 713f8e3..5e426ec 100644 --- a/src/world/WorldPatch.ts +++ b/src/world/WorldPatch.ts @@ -162,8 +162,6 @@ export class BlocksPatch { } } - getPatchCoords() {} - toStub() { const { key } = this return { @@ -184,6 +182,14 @@ export class BlocksPatch { return patch } + static asPatchCoords = (position: Vector3) => { + const { patchSize } = this + const orig_x = Math.floor(position.x / patchSize) + const orig_z = Math.floor(position.z / patchSize) + const patchCoords = new Vector2(orig_x, orig_z) + return patchCoords + } + static getPatchOrigin(input: Box3 | Vector3 | Vector2) { const { patchSize } = this const inputCopy: Vector3 | Box3 = From 14e29770528442df3e26b31edd68925df516ba70 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 9 Aug 2024 06:41:22 +0000 Subject: [PATCH 02/45] feat: custom size blocks container, built-in patch margin --- src/index.ts | 2 +- src/procgen/EntitiesMap.ts | 2 +- src/world/WorldApi.ts | 2 +- src/world/WorldCache.ts | 128 +++++++++------ src/world/WorldCompute.ts | 90 +++++------ src/world/{WorldPatch.ts => WorldData.ts} | 183 ++++++++++++++-------- 6 files changed, 244 insertions(+), 163 deletions(-) rename src/world/{WorldPatch.ts => WorldData.ts} (50%) diff --git a/src/index.ts b/src/index.ts index 36a45a8..f1a87ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { BlocksPatch } from './world/WorldPatch' +export { BlocksPatch } from './world/WorldData' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index d39f07f..35819e7 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -4,7 +4,7 @@ import { Box3, Vector2, Vector3 } from 'three' import { TreeGenerators } from '../tools/TreeGenerator' import { EntityType } from '../index' -import { BlocksPatch } from '../world/WorldPatch' +import { BlocksPatch } from '../world/WorldData' import { ProcLayer } from './ProcLayer' import { BlockType } from './Biome' diff --git a/src/world/WorldApi.ts b/src/world/WorldApi.ts index 5def29f..361eaa4 100644 --- a/src/world/WorldApi.ts +++ b/src/world/WorldApi.ts @@ -1,7 +1,7 @@ import { WorldCompute } from './WorldCompute' export enum WorldApiName { - PatchCompute = 'buildPatch', + PatchCompute = 'computePatch', BlocksBatchCompute = 'computeBlocksBatch', GroundBlockCompute = 'computeGroundBlock', OvergroundBlocksCompute = 'computeOvergroundBlocks', diff --git a/src/world/WorldCache.ts b/src/world/WorldCache.ts index dc5ef23..ddc8d24 100644 --- a/src/world/WorldCache.ts +++ b/src/world/WorldCache.ts @@ -1,4 +1,5 @@ import { Box3, Vector2, Vector3 } from 'three' +import { asVect3 } from '../common/utils' import { BlockType } from '../index' @@ -9,7 +10,7 @@ import { BlockStub, EntityChunk, PatchStub, -} from './WorldPatch' +} from './WorldData' /** * Blocks cache @@ -17,9 +18,9 @@ import { export class WorldCache { static patchLookupIndex: Record = {} static bbox = new Box3() // global cache extent + static lastCacheBox = new Box3() static pendingRefresh = false - static cacheCenter = new Vector2(0, 0) - static cachePowRadius = 1 + static cachePowRadius = 2 static cacheSize = BlocksPatch.patchSize * 5 // static worldApi = new WorldApi() @@ -34,9 +35,10 @@ export class WorldCache { static async *processBatchItems(batchContent: string[]) { for (const patchKey of batchContent) { + const emptyPatch = new BlocksPatch(patchKey) const patchStub = await WorldApi.instance.call( WorldApiName.PatchCompute, - [patchKey], + [emptyPatch.bbox]//[patchKey], ) yield patchStub as PatchStub } @@ -50,58 +52,75 @@ export class WorldCache { return batchRes } - static async refresh(bbox: Box3) { - const changes: any = { - batch: [], - count: 0, - } - if (!this.pendingRefresh) { - const range_min = BlocksPatch.asPatchCoords(bbox.min) - const range_max = BlocksPatch.asPatchCoords(bbox.max) - this.bbox = bbox - const backup = [] - for (let {x} = range_min; x < range_max.x + 1; x += 1) { - for (let {y} = range_min; y < range_max.y + 1; y += 1) { - // const patchStart = new Vector2(xmin, zmin) - const patchIndexKey = 'patch_' + x + '_' + y - // look for existing patch in current cache - const patch = this.patchLookupIndex[patchIndexKey] // || new BlocksPatch(patchStart) //BlocksPatch.getPatch(patchBbox, true) as BlocksPatch - if (!patch) { - // patch = new BlocksPatch(patchStart) - // add all patch needing to be filled up - changes.batch.push(patchIndexKey) - } else { - backup.push(patch) - } - } + static genPatchKeys(bbox: Box3) { + const batchKeys: Record = {}; + const halfDimensions = bbox.getSize(new Vector3()).divideScalar(2) + const range = BlocksPatch.asPatchCoords(halfDimensions) + const center = bbox.getCenter(new Vector3()) + const origin = BlocksPatch.asPatchCoords(center) + for (let xmin = origin.x - range.x; xmin < origin.x + range.x; xmin += 1) { + for (let zmin = origin.y - range.y; zmin < origin.y + range.y; zmin += 1) { + const patch_key = 'patch_' + xmin + '_' + zmin; + batchKeys[patch_key] = true } + } + return batchKeys + } + + static genDiffBatch(bbox: Box3) { + const prevBatchKeys = WorldCache.genPatchKeys(bbox) + const currBatchKeys = WorldCache.genPatchKeys(WorldCache.lastCacheBox) + const batchKeysDiff: Record = {} + // Object.keys(currBatchKeys).forEach(batchKey=>currBatchKeys[batchKey] = !prevBatchKeys[batchKey]) + Object.keys(currBatchKeys) + .filter(batchKey => !prevBatchKeys[batchKey]) + .forEach(batchKey => batchKeysDiff[batchKey] = true) + WorldCache.lastCacheBox = bbox + return batchKeysDiff + } - changes.count = - changes.batch.length + - Object.keys(WorldCache.patchLookupIndex).length - - backup.length - // update required - if (changes.count > 0) { - this.pendingRefresh = true - // const updated = existing.filter(patch => patch.state < PatchState.Finalised) - // const removedCount = Object.keys(WorldCache.patchLookupIndex).length - existing.length - WorldCache.patchLookupIndex = {} - backup.forEach( - patch => (WorldCache.patchLookupIndex[patch.key] = patch), - ) - - const batchIter = WorldCache.processBatchItems(changes.batch) - for await (const patchStub of batchIter) { - const patch = BlocksPatch.fromStub(patchStub) - WorldCache.patchLookupIndex[patch.key] = patch - // WorldCache.bbox.union(patch.bbox) - } - this.pendingRefresh = false + static async refresh( + center: Vector3, + // worldProxy: WorldProxy = PatchProcessing, + // asyncMode = false, + ) { + const { patchSize } = BlocksPatch + const { cachePowRadius } = this + // const cachePatchCount = Object.keys(this.patchLookupIndex).length + const range = Math.pow(2, cachePowRadius) + const origin = BlocksPatch.asPatchCoords(center) + const boxCenter = asVect3(origin).multiplyScalar(patchSize) + const boxDims = new Vector3(range, 0, range).multiplyScalar(2 * patchSize) + const bbox = new Box3().setFromCenterAndSize(boxCenter, boxDims) + const required = this.genPatchKeys(bbox) + Object.keys(required) + .forEach(patchKey => required[patchKey] = this.patchLookupIndex[patchKey]) + // exclude cached items from batch + const batchContent = this.pendingRefresh ? [] : Object.entries(required) + .filter(([, v]) => !v) + .map(([k,]) => k) + // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) + if (batchContent.length > 0) { + this.pendingRefresh = true + // this.cacheCenter = origin + // clear cache + WorldCache.patchLookupIndex = {} + const remaining = Object.values(required).filter(val => val) + // restore remaining items in cache + remaining.forEach( + patch => (WorldCache.patchLookupIndex[patch.key] = patch), + ) + const batchIter = WorldCache.processBatchItems(batchContent) + for await (const patchStub of batchIter) { + const patch = BlocksPatch.fromStub(patchStub) + WorldCache.patchLookupIndex[patch.key] = patch + WorldCache.bbox.union(patch.bbox) } + this.pendingRefresh = false } - return changes } + static getPatch(inputPoint: Vector2 | Vector3) { const point = new Vector3( inputPoint.x, @@ -213,4 +232,13 @@ export class WorldCache { } return block } + + static buildPlateau(patchKeys: string[]) { + const patches = patchKeys.map(patchKey => this.patchLookupIndex[patchKey]) + const bbox = patches.reduce( + (bbox, patch) => bbox.union(patch?.bbox || new Box3()), + new Box3(), + ) + console.log(patchKeys) + } } diff --git a/src/world/WorldCompute.ts b/src/world/WorldCompute.ts index 2b7d4ab..680fb23 100644 --- a/src/world/WorldCompute.ts +++ b/src/world/WorldCompute.ts @@ -9,7 +9,7 @@ import { RepeatableEntitiesMap, } from '../procgen/EntitiesMap' -import { BlocksPatch, EntityChunk } from './WorldPatch' +import { BlocksContainer, BlocksPatch, EntityChunk } from './WorldData' export class WorldCompute { static pendingTask = false @@ -26,6 +26,28 @@ export class WorldCompute { this.inputKeys = inputKeys } + static computeGroundBlock(blockPos: Vector3) { + const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) + const mainBiome = Biome.instance.getMainBiome(biomeContribs) + const rawVal = Heightmap.instance.getRawVal(blockPos) + const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) + 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 entityType = blockTypes.entities?.[0] as EntityType + // let offset = 0 + // if (lastBlock && entityType) { + + // } + // level += offset + const block = { level, type } + return block + } + static computeOvergroundBlocks(blockPos: Vector3) { let blocksBuffer: BlockType[] = [] // query entities at current block @@ -52,28 +74,6 @@ export class WorldCompute { return blocksBuffer } - static computeGroundBlock(blockPos: Vector3) { - const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) - const mainBiome = Biome.instance.getMainBiome(biomeContribs) - const rawVal = Heightmap.instance.getRawVal(blockPos) - const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) - 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 entityType = blockTypes.entities?.[0] as EntityType - // let offset = 0 - // if (lastBlock && entityType) { - - // } - // level += offset - const block = { level, type } - return block - } - static computeBlocksBatch(batchContent: [], includeEntities = true) { const batchRes = batchContent.map(({ x, z }) => { const block_pos = new Vector3(x, 0, z) @@ -91,15 +91,14 @@ export class WorldCompute { return batchRes } - static buildPatch(patchKey: string) { - const patch = new BlocksPatch(patchKey) - // asyncMode && (await new Promise(resolve => setTimeout(resolve, 0))) - WorldCompute.buildGroundPatch(patch) - WorldCompute.buildEntitiesChunks(patch) + static computePatch(bbox: Box3) { + const patch = new BlocksContainer(bbox) + WorldCompute.genGroundBlocks(patch) + WorldCompute.genEntitiesBlocks(patch) return patch } - static buildEntityChunk(patch: BlocksPatch, entity: EntityData) { + static buildEntityChunk(patch: BlocksContainer, entity: EntityData) { const entityChunk: EntityChunk = { bbox: new Box3(), data: [], @@ -121,8 +120,8 @@ export class WorldCompute { return entityChunk } - static buildEntitiesChunks(patch: BlocksPatch) { - const entitiesIter = RepeatableEntitiesMap.instance.iterate(patch.bbox) + static genEntitiesBlocks(blocksContainer: BlocksContainer) { + const entitiesIter = RepeatableEntitiesMap.instance.iterate(blocksContainer.bbox) for (const entity of entitiesIter) { // use global coords in case entity center is from adjacent patch const entityPos = entity.bbox.getCenter(new Vector3()) @@ -136,29 +135,29 @@ export class WorldCompute { if (entityType) { const dims = entity.bbox.getSize(new Vector3()) dims.y = 10 - const localBmin = entity.bbox.min.clone().sub(patch.bbox.min) + const localBmin = entity.bbox.min.clone().sub(blocksContainer.bbox.min) localBmin.y = Heightmap.instance.getGroundLevel(entityPos) const localBmax = localBmin.clone().add(dims) const localBbox = new Box3(localBmin, localBmax) entity.bbox = localBbox entity.type = entityType - const entityChunk = WorldCompute.buildEntityChunk(patch, entity) - patch.entitiesChunks.push(entityChunk) + const entityChunk = WorldCompute.buildEntityChunk(blocksContainer, entity) + blocksContainer.entitiesChunks.push(entityChunk) // let item: BlockIteratorRes = blocksIter.next() } } } /** - * Gen blocks data that will be sent to blocks cache + * Fill container with ground blocks */ - static buildGroundPatch(patch: BlocksPatch) { - const { min, max } = patch.bbox + static genGroundBlocks(blocksContainer: BlocksContainer) { + const { min, max } = blocksContainer.bbox // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const blocksPatchIter = patch.iterBlocks() + const blocksPatchIter = blocksContainer.iterBlocks(false, false) min.y = 512 max.y = 0 let blockIndex = 0 @@ -169,15 +168,16 @@ export class WorldCompute { const block = this.computeGroundBlock(blockPos) min.y = Math.min(min.y, block.level) max.y = Math.max(max.y, block.level) - patch.writeBlockAtIndex(blockIndex, block.level, block.type) + // blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) + blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) blockIndex++ } - patch.bbox.min = min - patch.bbox.max = max - patch.bbox.getSize(patch.dimensions) - // PatchBlocksCache.bbox.union(patch.bbox) + blocksContainer.bbox.min = min + blocksContainer.bbox.max = max + blocksContainer.bbox.getSize(blocksContainer.dimensions) + // PatchBlocksCache.bbox.union(blocksContainer.bbox) - // patch.state = PatchState.Filled - return patch + // blocksContainer.state = PatchState.Filled + return blocksContainer } } diff --git a/src/world/WorldPatch.ts b/src/world/WorldData.ts similarity index 50% rename from src/world/WorldPatch.ts rename to src/world/WorldData.ts index 5e426ec..6f0bf90 100644 --- a/src/world/WorldPatch.ts +++ b/src/world/WorldData.ts @@ -1,4 +1,5 @@ import { Box3, Vector2, Vector3 } from 'three' +import { parseThreeStub } from '../common/utils' import { BlockType } from '../procgen/Biome' @@ -31,38 +32,27 @@ export type PatchStub = { export type BlockIteratorRes = IteratorResult -export class BlocksPatch { - // eslint-disable-next-line no-use-before-define - // static cache: BlocksPatch[] = [] - static patchSize = Math.pow(2, 6) - static bbox = new Box3() - - coords: Vector2 - key: string +export class BlocksContainer { bbox: Box3 dimensions = new Vector3() + margin = 0 - groundBlocks = { - type: new Uint16Array(Math.pow(BlocksPatch.patchSize, 2)), - level: new Uint16Array(Math.pow(BlocksPatch.patchSize, 2)), + groundBlocks: { + type: Uint16Array, + level: Uint16Array, } entitiesChunks: EntityChunk[] = [] - constructor(patchKey: string) { - const { patchSize } = BlocksPatch - const patchOrigin = new Vector3( - parseInt(patchKey.split('_')[1] as string), - 0, - parseInt(patchKey.split('_')[2] as string), - ) - this.coords = new Vector2(patchOrigin.x, patchOrigin.z) - const bmin = patchOrigin.clone().multiplyScalar(patchSize) - const bmax = patchOrigin.clone().addScalar(1).multiplyScalar(patchSize) - bmax.y = 512 - this.key = patchKey - this.bbox = new Box3(bmin, bmax) + constructor(bbox: Box3, margin = 1) { + this.bbox = bbox this.bbox.getSize(this.dimensions) + this.margin = margin + const { extendedDims } = this + this.groundBlocks = { + type: new Uint16Array(extendedDims.x * extendedDims.z), + level: new Uint16Array(extendedDims.x * extendedDims.z), + } } writeBlockAtIndex( @@ -74,6 +64,22 @@ export class BlocksPatch { this.groundBlocks.type[blockIndex] = blockType } + get extendedBox() { + return this.bbox.clone().expandByScalar(this.margin) + } + + get extendedDims() { + return this.extendedBox.getSize(new Vector3()) + } + + getBlockIndex(localPos: Vector3) { + return (localPos.x + this.margin) * this.extendedDims.x + localPos.z + this.margin + } + + getLocalPos(pos: Vector3) { + return pos.clone().sub(this.bbox.min) + } + getBlock(localPos: Vector3) { let block if ( @@ -82,7 +88,7 @@ export class BlocksPatch { localPos.z >= 0 && localPos.z < this.dimensions.z ) { - const blockIndex = localPos.x * this.dimensions.x + localPos.z + const blockIndex = this.getBlockIndex(localPos) const pos = localPos.clone() pos.y = this.groundBlocks.level[blockIndex] || 0 const type = this.groundBlocks.type[blockIndex] @@ -115,11 +121,12 @@ export class BlocksPatch { 0, Math.min(bbox.max.z, useLocalPos ? patchSize : this.bbox.max.z), ) + for (let { x } = bmin; x < bmax.x; x++) { for (let { z } = bmin; z < bmax.z; z++) { const pos = new Vector3(x, 0, z) - const localPos = useLocalPos ? pos : pos.clone().sub(this.bbox.min) - const index = localPos.x * this.dimensions.x + localPos.z + const localPos = useLocalPos ? pos : this.getLocalPos(pos) + const index = this.getBlockIndex(localPos) const type = this.groundBlocks.type[index] || BlockType.NONE const level = this.groundBlocks.level[index] || 0 pos.y = level @@ -134,44 +141,73 @@ export class BlocksPatch { } } - *iterBlocks(useLocalCoords?: boolean) { + *iterBlocks(useLocalCoords?: boolean, skipMargins = true) { const bbox = useLocalCoords - ? new Box3(new Vector3(0), this.dimensions) - : this.bbox + ? new Box3(new Vector3(0), this.dimensions.clone()) + : this.bbox.clone() + bbox.expandByScalar(this.margin) let index = 0 - for (let { x } = bbox.min; x < bbox.max.x; x++) { - for (let { z } = bbox.min; z < bbox.max.z; z++) { - const pos = new Vector3(x, 0, z) - // highlight patch edges - // blockType = x === bbox.min.x ? BlockType.MUD : blockType - // blockType = x === bbox.max.x - 1 ? BlockType.ROCK : blockType - // blockType = z === bbox.min.z ? BlockType.MUD : blockType - // blockType = z === bbox.max.z - 1 ? BlockType.ROCK : blockType - const type = this.groundBlocks.type[index] || BlockType.NONE - const level = this.groundBlocks?.level[index] || 0 - pos.y = level - const blockData = { - index, - pos, - type, + const isMargin = (x: number, z: number) => this.margin > 0 && (x === bbox.min.x || x === bbox.max.x - 1 || z === bbox.min.z || z === bbox.max.z - 1) + + for (let x = bbox.min.x; x < bbox.max.x; x++) { + for (let z = bbox.min.z; z < bbox.max.z; z++) { + if (!skipMargins || !isMargin(x, z)) { + const pos = new Vector3(x, 0, z) + // highlight patch edges + // blockType = x === bbox.min.x ? BlockType.MUD : blockType + // blockType = x === bbox.max.x - 1 ? BlockType.ROCK : blockType + // blockType = z === bbox.min.z ? BlockType.MUD : blockType + // blockType = z === bbox.max.z - 1 ? BlockType.ROCK : blockType + const type = this.groundBlocks.type[index] || BlockType.NONE + const level = this.groundBlocks?.level[index] || 0 + pos.y = level + const blockData = { + index, + pos, + type, + } + yield blockData } index++ - yield blockData } } } - toStub() { - const { key } = this - return { - key, - } + static fromStub(stub: BlocksContainer) { + const { groundBlocks, entitiesChunks } = stub + const blocksContainer = new BlocksContainer(stub.bbox) + blocksContainer.groundBlocks = groundBlocks + blocksContainer.entitiesChunks = entitiesChunks + // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => + // patch.entitiesChunks.push(entityChunk), + // ) + return blocksContainer } - static fromStub(patchStub: PatchStub) { - const { key, groundBlocks, entitiesChunks } = patchStub - const patch = new BlocksPatch(key) +} + +export class BlocksPatch extends BlocksContainer { + // eslint-disable-next-line no-use-before-define + // static cache: BlocksPatch[] = [] + static patchSize = Math.pow(2, 6) + static bbox = new Box3() + + coords: Vector2 + key: string + + constructor(patchKey: string) { + super(BlocksPatch.getBboxFromPatchKey(patchKey))//.expandByScalar(1)) + this.key = patchKey + const patchCoords = BlocksPatch.parsePatchKey(patchKey) + this.coords = new Vector2(patchCoords.x, patchCoords.z) + } + + static override fromStub(patchStub: BlocksPatch) { + const { groundBlocks, entitiesChunks } = patchStub + const bbox = parseThreeStub(patchStub.bbox) + const patchKey = patchStub.key || this.computePatchKey(bbox) + const patch = new BlocksPatch(patchKey) patch.groundBlocks = groundBlocks patch.entitiesChunks = entitiesChunks patch.bbox.min.y = patchStub.bbox.min.y @@ -184,14 +220,32 @@ export class BlocksPatch { static asPatchCoords = (position: Vector3) => { const { patchSize } = this - const orig_x = Math.floor(position.x / patchSize) - const orig_z = Math.floor(position.z / patchSize) - const patchCoords = new Vector2(orig_x, orig_z) + const orig_x = Math.floor(position.x / patchSize); + const orig_z = Math.floor(position.z / patchSize); + const patchCoords = new Vector2(orig_x, orig_z); return patchCoords } - static getPatchOrigin(input: Box3 | Vector3 | Vector2) { - const { patchSize } = this + static parsePatchKey = (patchKey: string) => { + const patchOrigin = new Vector3( + parseInt(patchKey.split('_')[1] as string), + 0, + parseInt(patchKey.split('_')[2] as string), + ) + return patchOrigin + } + + static getBboxFromPatchKey = (patchKey: string) => { + const { patchSize } = BlocksPatch + const patchCoords = BlocksPatch.parsePatchKey(patchKey) + const bmin = patchCoords.clone().multiplyScalar(patchSize) + const bmax = patchCoords.clone().addScalar(1).multiplyScalar(patchSize) + bmax.y = 512 + const bbox = new Box3(bmin, bmax) + return bbox + } + + static computePatchKey(input: Box3 | Vector3 | Vector2) { const inputCopy: Vector3 | Box3 = input instanceof Vector2 ? new Vector3(input.x, 0, input.y) @@ -200,11 +254,10 @@ export class BlocksPatch { inputCopy instanceof Box3 ? (inputCopy as Box3).getCenter(new Vector3()) : (inputCopy as Vector3).clone() - let minx = point.x - (point.x % patchSize) - minx -= point.x < 0 && point.x % this.patchSize !== 0 ? patchSize : 0 - let minz = point.z - (point.z % patchSize) - minz -= point.z < 0 && point.z % this.patchSize !== 0 ? patchSize : 0 - const patchOrigin = new Vector2(minx, minz) - return patchOrigin + + const patchOrigin = this.asPatchCoords(point) + const { x, y } = patchOrigin + const patchKey = `patch_${x}_${y}` + return patchKey } } From bbeb023e06834100e8ac92e5749f527a88177f23 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 9 Aug 2024 16:23:09 +0000 Subject: [PATCH 03/45] feat: add patch container data struct + update cache --- src/index.ts | 2 +- src/world/WorldApi.ts | 2 +- src/world/WorldCache.ts | 114 ++++++++++++++------------------------ src/world/WorldCompute.ts | 4 +- src/world/WorldData.ts | 62 ++++++++++++++++++++- 5 files changed, 106 insertions(+), 78 deletions(-) diff --git a/src/index.ts b/src/index.ts index f1a87ea..1d68a7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { BlocksPatch } from './world/WorldData' +export { BlocksContainer, BlocksPatch } from './world/WorldData' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' diff --git a/src/world/WorldApi.ts b/src/world/WorldApi.ts index 361eaa4..4acebb6 100644 --- a/src/world/WorldApi.ts +++ b/src/world/WorldApi.ts @@ -4,7 +4,7 @@ export enum WorldApiName { PatchCompute = 'computePatch', BlocksBatchCompute = 'computeBlocksBatch', GroundBlockCompute = 'computeGroundBlock', - OvergroundBlocksCompute = 'computeOvergroundBlocks', + OvergroundBufferCompute = 'computeOvergroundBuffer', } /** diff --git a/src/world/WorldCache.ts b/src/world/WorldCache.ts index ddc8d24..02cbcdb 100644 --- a/src/world/WorldCache.ts +++ b/src/world/WorldCache.ts @@ -9,6 +9,7 @@ import { BlocksPatch, BlockStub, EntityChunk, + PatchContainer, PatchStub, } from './WorldData' @@ -16,23 +17,18 @@ import { * Blocks cache */ export class WorldCache { - static patchLookupIndex: Record = {} static bbox = new Box3() // global cache extent - static lastCacheBox = new Box3() + // static lastCacheBox = new Box3() static pendingRefresh = false static cachePowRadius = 2 static cacheSize = BlocksPatch.patchSize * 5 + static patchContainer = new PatchContainer(new Box3()) // static worldApi = new WorldApi() // groundBlocks: Uint16Array = new Uint16Array(Math.pow(PatchBase.patchSize, 2)) entitiesChunks: EntityChunk[] = [] - addPatch(patchStub: PatchStub) { - const patch = BlocksPatch.fromStub(patchStub) - WorldCache.bbox.union(patch.bbox) - } - static async *processBatchItems(batchContent: string[]) { for (const patchKey of batchContent) { const emptyPatch = new BlocksPatch(patchKey) @@ -52,74 +48,45 @@ export class WorldCache { return batchRes } - static genPatchKeys(bbox: Box3) { - const batchKeys: Record = {}; - const halfDimensions = bbox.getSize(new Vector3()).divideScalar(2) - const range = BlocksPatch.asPatchCoords(halfDimensions) - const center = bbox.getCenter(new Vector3()) - const origin = BlocksPatch.asPatchCoords(center) - for (let xmin = origin.x - range.x; xmin < origin.x + range.x; xmin += 1) { - for (let zmin = origin.y - range.y; zmin < origin.y + range.y; zmin += 1) { - const patch_key = 'patch_' + xmin + '_' + zmin; - batchKeys[patch_key] = true - } - } - return batchKeys - } - - static genDiffBatch(bbox: Box3) { - const prevBatchKeys = WorldCache.genPatchKeys(bbox) - const currBatchKeys = WorldCache.genPatchKeys(WorldCache.lastCacheBox) - const batchKeysDiff: Record = {} - // Object.keys(currBatchKeys).forEach(batchKey=>currBatchKeys[batchKey] = !prevBatchKeys[batchKey]) - Object.keys(currBatchKeys) - .filter(batchKey => !prevBatchKeys[batchKey]) - .forEach(batchKey => batchKeysDiff[batchKey] = true) - WorldCache.lastCacheBox = bbox - return batchKeysDiff - } - - static async refresh( - center: Vector3, - // worldProxy: WorldProxy = PatchProcessing, - // asyncMode = false, - ) { - const { patchSize } = BlocksPatch - const { cachePowRadius } = this - // const cachePatchCount = Object.keys(this.patchLookupIndex).length - const range = Math.pow(2, cachePowRadius) - const origin = BlocksPatch.asPatchCoords(center) - const boxCenter = asVect3(origin).multiplyScalar(patchSize) - const boxDims = new Vector3(range, 0, range).multiplyScalar(2 * patchSize) - const bbox = new Box3().setFromCenterAndSize(boxCenter, boxDims) - const required = this.genPatchKeys(bbox) - Object.keys(required) - .forEach(patchKey => required[patchKey] = this.patchLookupIndex[patchKey]) - // exclude cached items from batch - const batchContent = this.pendingRefresh ? [] : Object.entries(required) - .filter(([, v]) => !v) - .map(([k,]) => k) - // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) - if (batchContent.length > 0) { + static async populate(patchContainer: PatchContainer, dryRun = false) { + const batchContent = patchContainer.missingPatchKeys + if (!dryRun && batchContent.length > 0) { this.pendingRefresh = true - // this.cacheCenter = origin - // clear cache - WorldCache.patchLookupIndex = {} - const remaining = Object.values(required).filter(val => val) - // restore remaining items in cache - remaining.forEach( - patch => (WorldCache.patchLookupIndex[patch.key] = patch), - ) + const batchIter = WorldCache.processBatchItems(batchContent) + // populate cache for await (const patchStub of batchIter) { const patch = BlocksPatch.fromStub(patchStub) - WorldCache.patchLookupIndex[patch.key] = patch - WorldCache.bbox.union(patch.bbox) + patchContainer.patchLookup[patch.key] = patch + patchContainer.bbox.union(patch.bbox) } this.pendingRefresh = false } } + /** + * + * @param center + * @param dryRun + * @returns true if cache was update, false otherwise + */ + static async refresh( + bbox: Box3, + dryRun = false + ) { + if (!this.pendingRefresh) { + const patchContainer = new PatchContainer(bbox) + const patchDiff = patchContainer.diffWithOtherContainer(this.patchContainer) + // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) + if (Object.keys(patchDiff).length) { + patchContainer.fillFromExistingContainer(this.patchContainer) + this.patchContainer = patchContainer + const batchContent = await this.populate(patchContainer, dryRun) + return batchContent + } + } + return [] + } static getPatch(inputPoint: Vector2 | Vector3) { const point = new Vector3( @@ -128,7 +95,7 @@ export class WorldCache { inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, ) - const res = Object.values(this.patchLookupIndex).find( + const res = this.patchContainer.availablePatches.find( patch => point.x >= patch.bbox.min.x && point.z >= patch.bbox.min.z && @@ -142,7 +109,7 @@ export class WorldCache { const bbox = inputBbox.clone() bbox.min.y = 0 bbox.max.y = 512 - const res = Object.values(this.patchLookupIndex).filter(patch => + const res = this.patchContainer.availablePatches.filter(patch => patch.bbox.intersectsBox(bbox), ) return res @@ -176,9 +143,10 @@ export class WorldCache { } static getGroundBlock(globalPos: Vector3) { + const { bbox } = this.patchContainer let res - globalPos.y = WorldCache.bbox.getCenter(new Vector3()).y - if (WorldCache.bbox.containsPoint(globalPos)) { + globalPos.y = bbox.getCenter(new Vector3()).y + if (bbox.containsPoint(globalPos)) { const patch = WorldCache.getPatch(globalPos) if (patch) { const localPos = globalPos.clone().sub(patch.bbox.min) @@ -205,11 +173,11 @@ export class WorldCache { return res } - static async getOvergroundBlock(globalPos: Vector3) { + static async getTopLevelBlock(globalPos: Vector3) { const block = await WorldCache.getGroundBlock(globalPos) if (block) { const blocksBuffer = (await WorldApi.instance.call( - WorldApiName.OvergroundBlocksCompute, + WorldApiName.OvergroundBufferCompute, [block.pos], )) as BlockType[] const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) @@ -234,7 +202,7 @@ export class WorldCache { } static buildPlateau(patchKeys: string[]) { - const patches = patchKeys.map(patchKey => this.patchLookupIndex[patchKey]) + const patches = this.patchContainer.availablePatches const bbox = patches.reduce( (bbox, patch) => bbox.union(patch?.bbox || new Box3()), new Box3(), diff --git a/src/world/WorldCompute.ts b/src/world/WorldCompute.ts index 680fb23..7a48f61 100644 --- a/src/world/WorldCompute.ts +++ b/src/world/WorldCompute.ts @@ -48,7 +48,7 @@ export class WorldCompute { return block } - static computeOvergroundBlocks(blockPos: Vector3) { + static computeOvergroundBuffer(blockPos: Vector3) { let blocksBuffer: BlockType[] = [] // query entities at current block const entitiesIter = RepeatableEntitiesMap.instance.iterate(blockPos) @@ -79,7 +79,7 @@ export class WorldCompute { const block_pos = new Vector3(x, 0, z) const block = WorldCompute.computeGroundBlock(block_pos) if (includeEntities) { - const blocksBuffer = WorldCompute.computeOvergroundBlocks(block_pos) + const blocksBuffer = WorldCompute.computeOvergroundBuffer(block_pos) const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) if (lastBlockIndex >= 0) { block.level += lastBlockIndex diff --git a/src/world/WorldData.ts b/src/world/WorldData.ts index 6f0bf90..398192b 100644 --- a/src/world/WorldData.ts +++ b/src/world/WorldData.ts @@ -1,5 +1,5 @@ import { Box3, Vector2, Vector3 } from 'three' -import { parseThreeStub } from '../common/utils' +import { asVect3, parseThreeStub } from '../common/utils' import { BlockType } from '../procgen/Biome' @@ -261,3 +261,63 @@ export class BlocksPatch extends BlocksContainer { return patchKey } } + +export class PatchContainer { + bbox: Box3 + patchRange: Box3 + patchLookup: Record = {} + + constructor(bbox: Box3) { + this.bbox = bbox + const rangeMin = BlocksPatch.asPatchCoords(bbox.min) + const rangeMax = BlocksPatch.asPatchCoords(bbox.max) + this.patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) + this.init() + } + + init() { + // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) + // const range = BlocksPatch.asPatchCoords(halfDimensions) + // const center = this.bbox.getCenter(new Vector3()) + // const origin = BlocksPatch.asPatchCoords(center) + const { min, max } = this.patchRange + for (let x = min.x; x < max.x; x++) { + for (let z = min.z; z < max.z; z++) { + const patchKey = 'patch_' + x + '_' + z; + this.patchLookup[patchKey] = null + } + } + } + + get availablePatches() { + return Object.values(this.patchLookup).filter(val => val) as BlocksPatch[] + } + + get missingPatchKeys() { + return Object.keys(this.patchLookup).filter(key => !this.patchLookup[key]) + } + + get count() { + return Object.keys(this.patchLookup).length + } + + fillFromExistingContainer(otherContainer: PatchContainer) { + Object.keys(this.patchLookup) + .filter(patchKey => otherContainer.patchLookup[patchKey]) + .forEach(patchKey => this.patchLookup[patchKey] = otherContainer.patchLookup[patchKey] as BlocksPatch) + } + + diffWithOtherContainer(otherContainer: PatchContainer) { + const patchKeysDiff: Record = {} + // added keys e.g. keys in current container but not found in other + Object.keys(this.patchLookup) + .filter(patchKey => otherContainer.patchLookup[patchKey] === undefined) + .forEach(patchKey => patchKeysDiff[patchKey] = true) + // missing keys e.g. found in other container but not in current + Object.keys(otherContainer.patchLookup) + .filter(patchKey => this.patchLookup[patchKey] === undefined) + .forEach(patchKey => patchKeysDiff[patchKey] = false) + return patchKeysDiff + } + +} From 2a1b6e1d52907a63abb9121e6d9043daf8d9ba42 Mon Sep 17 00:00:00 2001 From: etienne Date: Mon, 12 Aug 2024 12:26:12 +0000 Subject: [PATCH 04/45] feat: changes to support board containers, include chunk tools --- src/data/BoardContainer.ts | 26 ++ src/{world/WorldData.ts => data/Patches.ts} | 149 ++++++---- src/index.ts | 7 +- src/utils/chunk_tools.ts | 252 ++++++++++++++++ src/utils/plateau_legacy.ts | 311 ++++++++++++++++++++ src/world/WorldCache.ts | 21 +- src/world/WorldCompute.ts | 6 +- 7 files changed, 700 insertions(+), 72 deletions(-) create mode 100644 src/data/BoardContainer.ts rename src/{world/WorldData.ts => data/Patches.ts} (66%) create mode 100644 src/utils/chunk_tools.ts create mode 100644 src/utils/plateau_legacy.ts diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts new file mode 100644 index 0000000..35a8fcf --- /dev/null +++ b/src/data/BoardContainer.ts @@ -0,0 +1,26 @@ +import { BlockType } from "../procgen/Biome"; +import { BlocksContainer, PatchContainer } from "./Patches"; + +export class BoardContainer extends PatchContainer { + + mergeBoardBlocks(blocksContainer: BlocksContainer) { + // for each patch override with blocks from blocks container + this.availablePatches.forEach(patch => { + const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + for (const target_block of blocksIter) { + const source_block = blocksContainer.getBlock(target_block.pos, false) + if (source_block && source_block.pos.y > 0 && target_block.index) { + let block_type = source_block.type ? BlockType.MUD : BlockType.NONE + block_type = source_block.type === BlockType.TREE_TRUNK ? BlockType.TREE_TRUNK : block_type + const block_level = blocksContainer.bbox.min.y//source_block?.pos.y + patch.writeBlockAtIndex(target_block.index, block_level, block_type) + // console.log(source_block?.pos.y) + } + } + }) + } + + interpolateBoardEdges(){ + + } +} \ No newline at end of file diff --git a/src/world/WorldData.ts b/src/data/Patches.ts similarity index 66% rename from src/world/WorldData.ts rename to src/data/Patches.ts index 398192b..73322a1 100644 --- a/src/world/WorldData.ts +++ b/src/data/Patches.ts @@ -6,6 +6,7 @@ import { BlockType } from '../procgen/Biome' export type BlockData = { pos: Vector3 type: BlockType + index?: number localPos?: Vector3 buffer?: BlockType[] } @@ -45,7 +46,7 @@ export class BlocksContainer { entitiesChunks: EntityChunk[] = [] constructor(bbox: Box3, margin = 1) { - this.bbox = bbox + this.bbox = bbox.clone() this.bbox.getSize(this.dimensions) this.margin = margin const { extendedDims } = this @@ -55,6 +56,13 @@ export class BlocksContainer { } } + duplicate() { + const duplicate = new BlocksContainer(this.bbox) + this.groundBlocks.level.forEach((v, i) => duplicate.groundBlocks.level[i] = v) + this.groundBlocks.type.forEach((v, i) => duplicate.groundBlocks.type[i] = v) + return duplicate + } + writeBlockAtIndex( blockIndex: number, blockLevel: number, @@ -72,6 +80,26 @@ export class BlocksContainer { return this.extendedBox.getSize(new Vector3()) } + get localExtendedBox() { + const bbox = new Box3(new Vector3(0), this.dimensions.clone()).expandByScalar(this.margin) + return bbox + } + + adaptCustomBox(bbox: Box3, useLocalPos = false) { + const { patchSize } = BlocksPatch + const bmin = new Vector3( + Math.max(Math.floor(bbox.min.x), useLocalPos ? 0 : this.bbox.min.x), + 0, + Math.max(Math.floor(bbox.min.z), useLocalPos ? 0 : this.bbox.min.z), + ) + const bmax = new Vector3( + Math.min(Math.floor(bbox.max.x), useLocalPos ? patchSize : this.bbox.max.x), + 0, + Math.min(Math.floor(bbox.max.z), useLocalPos ? patchSize : this.bbox.max.z), + ) + return new Box3(bmin, bmax) + } + getBlockIndex(localPos: Vector3) { return (localPos.x + this.margin) * this.extendedDims.x + localPos.z + this.margin } @@ -80,7 +108,8 @@ export class BlocksContainer { return pos.clone().sub(this.bbox.min) } - getBlock(localPos: Vector3) { + getBlock(pos: Vector3, useLocalPos = true) { + const localPos = useLocalPos ? pos : this.getLocalPos(pos) let block if ( localPos.x >= 0 && @@ -109,62 +138,28 @@ export class BlocksContainer { // bbox.max.y = Math.max(bbox.max.y, levelMax) } - *getBlocks(bbox: Box3, useLocalPos = false) { - const { patchSize } = BlocksPatch - const bmin = new Vector3( - Math.max(bbox.min.x, useLocalPos ? 0 : this.bbox.min.x), - 0, - Math.max(bbox.min.z, useLocalPos ? 0 : this.bbox.min.z), - ) - const bmax = new Vector3( - Math.min(bbox.max.x, useLocalPos ? patchSize : this.bbox.max.x), - 0, - Math.min(bbox.max.z, useLocalPos ? patchSize : this.bbox.max.z), - ) - - for (let { x } = bmin; x < bmax.x; x++) { - for (let { z } = bmin; z < bmax.z; z++) { - const pos = new Vector3(x, 0, z) - const localPos = useLocalPos ? pos : this.getLocalPos(pos) - const index = this.getBlockIndex(localPos) - const type = this.groundBlocks.type[index] || BlockType.NONE - const level = this.groundBlocks.level[index] || 0 - pos.y = level - localPos.y = level - const blockData: BlockData = { - pos, - localPos, - type, - } - yield blockData - } - } - } + *iterOverBlocks(customBox?: Box3, useLocalPos = false, skipMargin = true) { + const bbox = customBox ? this.adaptCustomBox(customBox, useLocalPos) : + useLocalPos ? this.localExtendedBox : this.extendedBox - *iterBlocks(useLocalCoords?: boolean, skipMargins = true) { - const bbox = useLocalCoords - ? new Box3(new Vector3(0), this.dimensions.clone()) - : this.bbox.clone() - bbox.expandByScalar(this.margin) + const isMarginBlock = ({ x, z }: { x: number, z: number }) => !customBox && this.margin > 0 + && (x === bbox.min.x || x === bbox.max.x - 1 || z === bbox.min.z || z === bbox.max.z - 1) let index = 0 - const isMargin = (x: number, z: number) => this.margin > 0 && (x === bbox.min.x || x === bbox.max.x - 1 || z === bbox.min.z || z === bbox.max.z - 1) - - for (let x = bbox.min.x; x < bbox.max.x; x++) { - for (let z = bbox.min.z; z < bbox.max.z; z++) { - if (!skipMargins || !isMargin(x, z)) { - const pos = new Vector3(x, 0, z) - // highlight patch edges - // blockType = x === bbox.min.x ? BlockType.MUD : blockType - // blockType = x === bbox.max.x - 1 ? BlockType.ROCK : blockType - // blockType = z === bbox.min.z ? BlockType.MUD : blockType - // blockType = z === bbox.max.z - 1 ? BlockType.ROCK : blockType + for (let { x } = bbox.min; x < bbox.max.x; x++) { + for (let { z } = bbox.min; z < bbox.max.z; z++) { + const pos = new Vector3(x, 0, z) + if (!skipMargin || !isMarginBlock(pos)) { + const localPos = useLocalPos ? pos : this.getLocalPos(pos) + index = customBox ? this.getBlockIndex(localPos) : index const type = this.groundBlocks.type[index] || BlockType.NONE - const level = this.groundBlocks?.level[index] || 0 + const level = this.groundBlocks.level[index] || 0 pos.y = level - const blockData = { + localPos.y = level + const blockData: BlockData = { index, pos, + localPos, type, } yield blockData @@ -176,7 +171,7 @@ export class BlocksContainer { static fromStub(stub: BlocksContainer) { const { groundBlocks, entitiesChunks } = stub - const blocksContainer = new BlocksContainer(stub.bbox) + const blocksContainer = new BlocksContainer(parseThreeStub(stub.bbox)) blocksContainer.groundBlocks = groundBlocks blocksContainer.entitiesChunks = entitiesChunks // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => @@ -203,6 +198,13 @@ export class BlocksPatch extends BlocksContainer { this.coords = new Vector2(patchCoords.x, patchCoords.z) } + override duplicate() { + const duplicate = new BlocksPatch(this.key) + this.groundBlocks.level.forEach((v, i) => duplicate.groundBlocks.level[i] = v) + this.groundBlocks.type.forEach((v, i) => duplicate.groundBlocks.type[i] = v) + return duplicate + } + static override fromStub(patchStub: BlocksPatch) { const { groundBlocks, entitiesChunks } = patchStub const bbox = parseThreeStub(patchStub.bbox) @@ -270,7 +272,7 @@ export class PatchContainer { constructor(bbox: Box3) { this.bbox = bbox const rangeMin = BlocksPatch.asPatchCoords(bbox.min) - const rangeMax = BlocksPatch.asPatchCoords(bbox.max) + const rangeMax = BlocksPatch.asPatchCoords(bbox.max).addScalar(1) this.patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) this.init() } @@ -301,13 +303,43 @@ export class PatchContainer { return Object.keys(this.patchLookup).length } - fillFromExistingContainer(otherContainer: PatchContainer) { - Object.keys(this.patchLookup) - .filter(patchKey => otherContainer.patchLookup[patchKey]) - .forEach(patchKey => this.patchLookup[patchKey] = otherContainer.patchLookup[patchKey] as BlocksPatch) + get patchKeys() { + return Object.keys(this.patchLookup) } - diffWithOtherContainer(otherContainer: PatchContainer) { + // autoFill(fillingVal=0){ + // this.patchKeys.forEach(key=>this.patchLookup[key] = new BlocksPatch(key)) + // this.availablePatches.forEach(patch=>patch.iterOverBlocks) + // } + + fillFromPatches(patches: BlocksPatch[], cloneObjects = false) { + const { min, max } = this.bbox + patches.filter(patch => this.patchLookup[patch.key] !== undefined) + .forEach(patch => { + this.patchLookup[patch.key] = cloneObjects ? patch.duplicate() : patch + min.y = Math.min(patch.bbox.min.y, min.y) + max.y = Math.max(patch.bbox.max.y, max.y) + }) + } + + mergeBlocks(blocksContainer: BlocksContainer) { + // for each patch override with blocks from blocks container + this.availablePatches.forEach(patch => { + const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + for (const target_block of blocksIter) { + const source_block = blocksContainer.getBlock(target_block.pos, false) + if (source_block && source_block.pos.y > 0 && target_block.index) { + let block_type = source_block.type ? BlockType.SAND : BlockType.NONE + block_type = source_block.type === BlockType.TREE_TRUNK ? BlockType.TREE_TRUNK : block_type + const block_level = blocksContainer.bbox.min.y//source_block?.pos.y + patch.writeBlockAtIndex(target_block.index, block_level, block_type) + // console.log(source_block?.pos.y) + } + } + }) + } + + diffWithPatchContainer(otherContainer: PatchContainer) { const patchKeysDiff: Record = {} // added keys e.g. keys in current container but not found in other Object.keys(this.patchLookup) @@ -319,5 +351,4 @@ export class PatchContainer { .forEach(patchKey => patchKeysDiff[patchKey] = false) return patchKeysDiff } - } diff --git a/src/index.ts b/src/index.ts index 1d68a7b..70b6056 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,8 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { BlocksContainer, BlocksPatch } from './world/WorldData' +export { BlocksContainer, BlocksPatch, PatchContainer } from './data/Patches' +export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' @@ -9,5 +10,9 @@ export { WorldApi, WorldWorkerApi, WorldApiName } from './world/WorldApi' export { WorldCache } from './world/WorldCache' export { WorldCompute } from './world/WorldCompute' export * as WorldUtils from './common/utils' +export * as ChunkTools from './utils/chunk_tools' +// export * as BoardUtils from './utils/BoardUtils' +export * as PlateauLegacy from './utils/plateau_legacy' + // export type { MappingConf, MappingData, MappingRanges } from "./common/types" // export { DevHacks } from './tools/DevHacks' diff --git a/src/utils/chunk_tools.ts b/src/utils/chunk_tools.ts new file mode 100644 index 0000000..0bb5543 --- /dev/null +++ b/src/utils/chunk_tools.ts @@ -0,0 +1,252 @@ +import { Box3, MathUtils, Vector3 } from "three" +import { + BlocksContainer, + BlocksPatch, + BlockType, + WorldUtils +} from '../index' + +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND + +// for debug use only +const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { + return DBG_BORDERS_HIGHLIGHT_COLOR && (localPos.x === 1 || localPos.z === 1) ? + DBG_BORDERS_HIGHLIGHT_COLOR : blockType +} + +const writeChunkBlocks = ( + chunkData: Uint16Array, + chunkBbox: Box3, + blockLocalPos: Vector3, + groundType: BlockType, + bufferOver = [], +) => { + const chunk_size = Math.round(Math.pow(chunkData.length, 1 / 3)) + + let written_blocks_count = 0 + + const level = MathUtils.clamp( + blockLocalPos.y + bufferOver.length, + chunkBbox.min.y, + chunkBbox.max.y, + ) + let buff_index = Math.max(level - blockLocalPos.y, 0) + let h = level - chunkBbox.min.y // local height + // debug_mode && is_edge(local_pos.z, local_pos.x, h, patch_size - 2) + // ? BlockType.SAND + // : block_cache.type + + 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] : groundType + const skip = + buff_index > 0 && + chunkData[blocksIndex] !== undefined && + !bufferOver[buff_index] + if (!skip) { + chunkData[blocksIndex] = blockType || BlockType.NONE + // ? voxelmapDataPacking.encode(false, blockType) + // : voxelmapDataPacking.encodeEmpty() + blockType && written_blocks_count++ + } + buff_index-- + h-- + } + return written_blocks_count +} + +const fillGroundData = (blocksContainer: BlocksContainer, chunkData: Uint16Array, chunkBox: Box3) => { + let written_blocks_count = 0 + const blocks_iter = blocksContainer.iterOverBlocks(undefined, true, false) + for (const block of blocks_iter) { + const blockLocalPos = block.pos + blockLocalPos.x += 1 + // blockLocalPos.y = patch.bbox.max.y + blockLocalPos.z += 1 + const blockType = highlightPatchBorders(blockLocalPos, block.type) || block.type + written_blocks_count += writeChunkBlocks( + chunkData, + chunkBox, + blockLocalPos, + blockType, + ) + } + return written_blocks_count +} + +const fillEntitiesData = (blocksContainer: BlocksContainer, chunkData: Uint16Array, chunkBox: Box3) => { + let written_blocks_count = 0 + // iter over container entities + for (const entity_chunk of blocksContainer.entitiesChunks) { + // const { min, max } = entity_chunk.bbox + // const bmin = new Vector3(...Object.values(min)) + // const bmax = new Vector3(...Object.values(max)) + // const entity_bbox = new Box3(bmin, bmax) + // find overlapping blocks between entity and container + const blocks_iter = blocksContainer.iterOverBlocks(entity_chunk.bbox, true) + let chunk_index = 0 + // iter over entity blocks + for (const block of blocks_iter) { + const bufferStr = entity_chunk.data[chunk_index] + const buffer = + bufferStr.length > 0 && + bufferStr.split(',').map(char => parseInt(char)) + if (buffer.length > 0) { + block.buffer = buffer + block.localPos.x += 1 + block.localPos.z += 1 + // bmin.y = block.localPos.y + written_blocks_count += writeChunkBlocks( + chunkData, + chunkBox, + block.localPos, + block.type, + block.buffer, + ) + } + chunk_index++ + } + } + return written_blocks_count +} + +export const getChunkBbox = (chunk_id: Vector3) => { + const bmin = chunk_id.clone().multiplyScalar(BlocksPatch.patchSize) + const bmax = chunk_id.clone().addScalar(1).multiplyScalar(BlocksPatch.patchSize) + const chunkBbox = new Box3(bmin, bmax) + chunkBbox.expandByScalar(1) + return chunkBbox +} + +export function makeChunk(blocksContainer: BlocksContainer, chunk_id: Vector3) { + const chunkBox = getChunkBbox(chunk_id) + const final_chunk = makeCustomChunk(blocksContainer, chunkBox) + final_chunk.id = chunk_id + return final_chunk +} + +export function makeCustomChunk(blocksContainer: BlocksContainer, chunkBox: Box3) { + const chunk_dims = chunkBox.getSize(new Vector3()) + const chunkData = new Uint16Array(chunk_dims.x * chunk_dims.y * chunk_dims.z) + let total_written_blocks_count = 0 + // const debug_mode = true + + // const is_edge = (row, col, h, patch_size) => + // row === 1 || row === patch_size || col === 1 || col === patch_size + // || h === 1 + // || h === patch_size - 2 + + // const patch = PatchBlocksCache.instances.find( + // patch => + // patch.bbox.min.x === bbox.min.x + 1 && + // patch.bbox.min.z === bbox.min.z + 1 && + // patch.bbox.max.x === bbox.max.x - 1 && + // patch.bbox.max.z === bbox.max.z - 1 && + // patch.bbox.intersectsBox(bbox), + // ) + + // multi-pass chunk filling + if (blocksContainer) { + // ground pass + total_written_blocks_count += fillGroundData( + blocksContainer, + chunkData, + chunkBox, + ) + // overground entities pass + total_written_blocks_count += fillEntitiesData( + blocksContainer, + chunkData, + chunkBox, + ) + } + // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) + // const dimensions = new Vector3(size, size, size) + const chunk = { + data: chunkData, + size: chunk_dims, + isEmpty: total_written_blocks_count === 0, + } + return chunk +} + +export function genChunkIds(patch: BlocksPatch, ymin: number, ymax: number) { + const chunk_ids = [] + if (patch) { + for (let y = ymax; y >= ymin; y--) { + const chunk_coords = WorldUtils.asVect3(patch.coords, y) + chunk_ids.push(chunk_coords) + } + } + return chunk_ids +} + +// const plateau_ground_pass = (blocksContainer, chunk) => { +// const patch_center = blocksContainer.bbox.getCenter(new Vector3()) // patch.bbox.min.y +// const plateau_height = Math.floor(patch_center.y) +// const iter = blocksContainer.iterOverBlocks(undefined, true) +// let res = iter.next() +// while (!res.done) { +// const block_data = res.value +// const block_pos = block_data.pos.clone() +// block_pos.x += 1 +// block_pos.y = plateau_height +// block_pos.z += 1 +// const blockType = block_data.type +// writeChunkBlocks(chunk, block_pos, blockType) +// res = iter.next() +// } +// } + +// const buff_iter = patch_bis.overBlocksIter() +// for (const blk of buff_iter) { +// blk.localPos.x += 1 +// blk.localPos.z += 1 +// writeChunkBlocks(chunk, blk.localPos, blk.type, blk.buffer) +// } + +// export function get_plateau_chunks(plateau_keys) { +// const chunks = [] +// plateau_keys.forEach(patch_key => { +// const patch = WorldCache.patchLookupIndex[patch_key] +// const chunks_ids = genChunkIds(patch_key) +// patch && +// chunks_ids.forEach(chunk_coords => { +// let is_empty = true +// const bmin = chunk_coords.clone().multiplyScalar(world_patch_size) +// const bmax = chunk_coords +// .clone() +// .addScalar(1) +// .multiplyScalar(world_patch_size) +// const chunkBbox = new Box3(bmin, bmax) +// chunkBbox.expandByScalar(1) +// const dimensions = chunkBbox.getSize(new Vector3()) +// const data = new Uint16Array(dimensions.x * dimensions.y * dimensions.z) +// const chunk = { bbox: chunkBbox, data } + +// // multi-pass chunk filling +// if (patch) { +// // ground pass +// // ground_blocks_pass(patch, chunk) +// plateau_ground_pass(patch, chunk) +// // overground entities pass +// // plateau_entities_pass(patch, chunk) +// // // extra blocks at edges from adjacent patches +// edges_blocks_pass(chunk) +// is_empty = false +// } +// // fill_chunk_plateau_from_patch(patch, chunkBbox) +// const final_chunk = { +// id: chunk_coords, +// data, +// size: dimensions, +// isEmpty: false, +// } +// chunks.push(final_chunk) +// }) +// }) +// return chunks +// } diff --git a/src/utils/plateau_legacy.ts b/src/utils/plateau_legacy.ts new file mode 100644 index 0000000..b75fd3b --- /dev/null +++ b/src/utils/plateau_legacy.ts @@ -0,0 +1,311 @@ +// import * as THREE from '../../../three-usage'; +// import { voxelmapDataPacking, type IVoxelMap } from '../i-voxelmap'; + +import { Box3, Vector3, Vector3Like } from 'three' +import { ChunkTools, WorldApi, WorldApiName } from '../index' +import { BlocksContainer } from '../data/Patches' + +enum EPlateauSquareType { + FLAT = 0, + HOLE = 1, + OBSTACLE = 2, +} + +type PlateauSquare = { + readonly type: EPlateauSquareType + readonly materialId: number +} + +type ColumnId = { readonly x: number; readonly z: number } + +type Plateau = { + readonly id: number + readonly size: { readonly x: number; readonly z: number } + readonly squares: ReadonlyArray + readonly origin: Vector3Like +} + +type PlateauSquareExtended = PlateauSquare & { + readonly floorY: number + readonly generation: number +} + +let plateauxCount = 0 + +async function computePlateau( + originWorld: Vector3Like, +): Promise { + originWorld = { + x: Math.floor(originWorld.x), + y: Math.floor(originWorld.y), + z: Math.floor(originWorld.z), + } + + let currentGeneration = 0 + const maxDeltaY = 4 + const plateauHalfSize = 31 + const plateauSize = { x: 2 * plateauHalfSize + 1, z: 2 * plateauHalfSize + 1 } + const plateauSquares: PlateauSquareExtended[] = [] + for (let iZ = 0; iZ < plateauSize.z; iZ++) { + for (let iX = 0; iX < plateauSize.x; iX++) { + plateauSquares.push({ + type: EPlateauSquareType.HOLE, + materialId: 0, + floorY: NaN, + generation: currentGeneration, + }) + } + } + const tryGetIndex = (relativePos: ColumnId) => { + const plateauCoords = { + x: relativePos.x + plateauHalfSize, + z: relativePos.z + plateauHalfSize, + } + if ( + plateauCoords.x < 0 || + plateauCoords.z < 0 || + plateauCoords.x >= plateauSize.x || + plateauCoords.z >= plateauSize.z + ) { + return null + } + return plateauCoords.x + plateauCoords.z * plateauSize.x + } + const getIndex = (relativePos: ColumnId) => { + const index = tryGetIndex(relativePos) + if (index === null) { + throw new Error() + } + return index + } + const setPlateauSquare = ( + relativePos: ColumnId, + square: PlateauSquareExtended, + ) => { + const index = getIndex(relativePos) + plateauSquares[index] = { ...square } + } + const getPlateauSquare = (relativePos: ColumnId) => { + const index = getIndex(relativePos) + return plateauSquares[index]! + } + const tryGetPlateauSquare = (relativePos: ColumnId) => { + const index = tryGetIndex(relativePos) + if (index === null) { + return null + } + return plateauSquares[index] + } + + const dataMargin = plateauHalfSize + 5 + const dataFromWorld = new Vector3().copy(originWorld).subScalar(dataMargin) + const dataToWorld = new Vector3().copy(originWorld).addScalar(dataMargin) + const dataBbox = new Box3(dataFromWorld, dataToWorld) + const containerStub = await WorldApi.instance.call( + WorldApiName.PatchCompute, + [dataBbox]//[patchKey], + ) + const blocksContainer = BlocksContainer.fromStub(containerStub) + const chunk = ChunkTools.makeCustomChunk(blocksContainer, dataBbox) + const data = chunk//await map.getLocalMapData(dataFromWorld, dataToWorld) + const dataSize = dataToWorld.clone().sub(dataFromWorld) + + const sampleData = (worldPos: Vector3Like) => { + const dataPos = new Vector3().copy(worldPos).sub(dataFromWorld) + if ( + dataPos.x < 0 || + dataPos.y < 0 || + dataPos.z < 0 || + dataPos.x >= dataSize.x || + dataPos.y >= dataSize.y || + dataPos.z >= dataSize.z + ) { + throw new Error() + } + const index = + dataPos.x + dataPos.y * dataSize.x + dataPos.z * dataSize.x * dataSize.y + return data.data[index]! + } + + { + const originWorldCoords = { + x: originWorld.x, + y: originWorld.y, + z: originWorld.z, + } + let originSample = sampleData(originWorldCoords) + let deltaY = 0 + while (!originSample && deltaY < maxDeltaY) { + originWorldCoords.y-- + deltaY++ + originSample = sampleData(originWorldCoords) + } + if (!originSample) { + throw new Error() + } + setPlateauSquare( + { x: 0, z: 0 }, + { + type: EPlateauSquareType.FLAT, + materialId: originSample, + generation: currentGeneration, + floorY: originWorldCoords.y - 1, + }, + ) + } + const originY = getPlateauSquare({ x: 0, z: 0 })!.floorY + + const computePlateauSquare = ( + relativePos: ColumnId, + ): PlateauSquareExtended | null => { + const square = getPlateauSquare(relativePos) + if (square.type !== EPlateauSquareType.HOLE) { + // this square has been computed already + return null + } + + // if this square has not been computed yet + const xm = tryGetPlateauSquare({ x: relativePos.x - 1, z: relativePos.z }) + const xp = tryGetPlateauSquare({ x: relativePos.x + 1, z: relativePos.z }) + const zm = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z - 1 }) + const zp = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z + 1 }) + + const worldPos = { x: 0, y: 0, z: 0 } + worldPos.x = relativePos.x + originWorld.x + worldPos.z = relativePos.z + originWorld.z + + for (const neighbour of [xm, xp, zm, zp]) { + if ( + neighbour?.type === EPlateauSquareType.FLAT && + neighbour.generation === currentGeneration - 1 + ) { + worldPos.y = neighbour.floorY + const generation = currentGeneration + const sampleY = sampleData(worldPos) + + if (sampleY) { + let firstSample: number | null = null + let lastSample = sampleY + for (let deltaY = 1; deltaY < maxDeltaY; deltaY++) { + const sample = sampleData({ + x: worldPos.x, + y: worldPos.y + deltaY, + z: worldPos.z, + }) + if (!sample) { + return { + type: EPlateauSquareType.FLAT, + materialId: lastSample, + floorY: worldPos.y + deltaY - 1, + generation, + } + } else { + firstSample = firstSample ?? sample + lastSample = sample + } + } + + if (!firstSample) { + throw new Error() + } + + return { + type: EPlateauSquareType.OBSTACLE, + materialId: firstSample, + floorY: worldPos.y, + generation, + } + } else { + for (let deltaY = -1; deltaY > -maxDeltaY; deltaY--) { + const sample = sampleData({ + x: worldPos.x, + y: worldPos.y + deltaY, + z: worldPos.z, + }) + if (sample) { + return { + type: EPlateauSquareType.FLAT, + materialId: sample, + floorY: worldPos.y + deltaY, + generation, + } + } + } + + return { + type: EPlateauSquareType.HOLE, + materialId: 0, + floorY: NaN, + generation, + } + } + } + } + + return null + } + + let somethingChanged = false + do { + somethingChanged = false + currentGeneration++ + + const relativePos = { x: 0, z: 0 } + for ( + relativePos.z = -plateauHalfSize; + relativePos.z <= plateauHalfSize; + relativePos.z++ + ) { + for ( + relativePos.x = -plateauHalfSize; + relativePos.x <= plateauHalfSize; + relativePos.x++ + ) { + if ( + Math.sqrt( + relativePos.x * relativePos.x + relativePos.z * relativePos.z, + ) >= + plateauHalfSize - 1 + ) { + continue + } + + const square = computePlateauSquare(relativePos) + if ( + square && + !isNaN(square.floorY) && + Math.abs(square.floorY - originY) < maxDeltaY + ) { + somethingChanged = true + setPlateauSquare(relativePos, square) + } + } + } + } while (somethingChanged) + + const minY = plateauSquares.reduce( + (y: number, square: PlateauSquareExtended) => { + if (!isNaN(square.floorY)) { + return Math.min(y, square.floorY) + } + return y + }, + originY, + ) + const plateauYShift = minY - originY - 1 + + const plateauOrigin = new Vector3( + originWorld.x - plateauHalfSize, + originWorld.y + plateauYShift, + originWorld.z - plateauHalfSize, + ) + + return { + id: plateauxCount++, + size: plateauSize, + squares: plateauSquares, + origin: plateauOrigin, + } +} + +export { computePlateau, EPlateauSquareType, type Plateau, type PlateauSquare } diff --git a/src/world/WorldCache.ts b/src/world/WorldCache.ts index 02cbcdb..e3bdc74 100644 --- a/src/world/WorldCache.ts +++ b/src/world/WorldCache.ts @@ -1,6 +1,4 @@ import { Box3, Vector2, Vector3 } from 'three' -import { asVect3 } from '../common/utils' - import { BlockType } from '../index' import { WorldApi, WorldApiName } from './WorldApi' @@ -11,7 +9,7 @@ import { EntityChunk, PatchContainer, PatchStub, -} from './WorldData' +} from '../data/Patches' /** * Blocks cache @@ -62,6 +60,7 @@ export class WorldCache { } this.pendingRefresh = false } + return batchContent } /** @@ -74,18 +73,22 @@ export class WorldCache { bbox: Box3, dryRun = false ) { + const changes: any = { + count: 0, + batch: [] + } if (!this.pendingRefresh) { const patchContainer = new PatchContainer(bbox) - const patchDiff = patchContainer.diffWithOtherContainer(this.patchContainer) + const patchDiff = patchContainer.diffWithPatchContainer(this.patchContainer) + changes.count = Object.keys(patchDiff).length // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) - if (Object.keys(patchDiff).length) { - patchContainer.fillFromExistingContainer(this.patchContainer) + if (changes.count) { + patchContainer.fillFromPatches(this.patchContainer.availablePatches) this.patchContainer = patchContainer - const batchContent = await this.populate(patchContainer, dryRun) - return batchContent + changes.batch = await this.populate(patchContainer, dryRun) } } - return [] + return changes } static getPatch(inputPoint: Vector2 | Vector3) { diff --git a/src/world/WorldCompute.ts b/src/world/WorldCompute.ts index 7a48f61..5b4d69c 100644 --- a/src/world/WorldCompute.ts +++ b/src/world/WorldCompute.ts @@ -9,7 +9,7 @@ import { RepeatableEntitiesMap, } from '../procgen/EntitiesMap' -import { BlocksContainer, BlocksPatch, EntityChunk } from './WorldData' +import { BlocksContainer, BlocksPatch, EntityChunk } from '../data/Patches' export class WorldCompute { static pendingTask = false @@ -103,7 +103,7 @@ export class WorldCompute { bbox: new Box3(), data: [], } - const blocksIter = patch.getBlocks(entity.bbox, true) + const blocksIter = patch.iterOverBlocks(entity.bbox, true) for (const block of blocksIter) { const blocksBuffer = EntitiesMap.fillBlockBuffer(block.pos, entity, []) patch.bbox.max.y = Math.max( @@ -157,7 +157,7 @@ export class WorldCompute { // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const blocksPatchIter = blocksContainer.iterBlocks(false, false) + const blocksPatchIter = blocksContainer.iterOverBlocks(undefined, false, false) min.y = 512 max.y = 0 let blockIndex = 0 From 76197412b47e4cb754481dfec675ea8e5e634473 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 13 Aug 2024 18:28:38 +0000 Subject: [PATCH 05/45] feat: mega-refactor of compute API, data containers (cache,board) + built-in chunk export --- src/common/types.ts | 12 +- src/common/utils.ts | 48 ++++- src/compute/WorldComputeApi.ts | 123 ++++++++++++ src/compute/world-compute.ts | 154 +++++++++++++++ src/data/BoardContainer.ts | 64 +++++- src/data/CacheContainer.ts | 163 ++++++++++++++++ src/data/{Patches.ts => DataContainers.ts} | 81 ++++++-- src/index.ts | 8 +- src/procgen/EntitiesMap.ts | 2 +- src/utils/chunk_tools.ts | 60 +++--- src/utils/plateau_legacy.ts | 11 +- src/world/WorldApi.ts | 80 -------- src/world/WorldCache.ts | 215 --------------------- src/world/WorldCompute.ts | 183 ------------------ 14 files changed, 654 insertions(+), 550 deletions(-) create mode 100644 src/compute/WorldComputeApi.ts create mode 100644 src/compute/world-compute.ts create mode 100644 src/data/CacheContainer.ts rename src/data/{Patches.ts => DataContainers.ts} (85%) delete mode 100644 src/world/WorldApi.ts delete mode 100644 src/world/WorldCache.ts delete mode 100644 src/world/WorldCompute.ts diff --git a/src/common/types.ts b/src/common/types.ts index ee28251..0acb930 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,4 +1,4 @@ -import { Vector3 } from 'three' +import { Vector2, Vector3 } from 'three' import { BiomeType, BlockType } from '../procgen/Biome' @@ -107,3 +107,13 @@ export enum EntityType { TREE_APPLE = 'apple_tree', TREE_PINE = 'pine_tree', } + +export type PatchKey = string +export type PatchId = Vector2 +export type ChunkKey = string +export type ChunkId = Vector3 + +export type WorldChunk = { + key: ChunkKey, + data: Uint16Array | null +} diff --git a/src/common/utils.ts b/src/common/utils.ts index 6acd5fc..f296068 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -3,8 +3,11 @@ import { Box3, Vector2, Vector3, Vector3Like } from 'three' import { Adjacent2dPos, Adjacent3dPos, + ChunkId, + ChunkKey, MappingRange, MappingRanges, + PatchId, } from './types' // Clamp number between two values: @@ -184,8 +187,12 @@ const bboxContainsPointXZ = (bbox: Box3, point: Vector3) => { ) } -const asVect3 = (vect2: Vector2, yVal = 0) => { - return new Vector3(vect2.x, yVal, vect2.y) +const vect3ToVect2 = (v3: Vector3) => { + return new Vector2(v3.x, v3.z) +} + +const vect2ToVect3 = (v2: Vector2, yVal = 0) => { + return new Vector3(v2.x, yVal, v2.y) } const parseVect3Stub = (stub: Vector3Like) => { @@ -219,6 +226,36 @@ const parseThreeStub = (stub: any) => { return parseBox3Stub(stub) || parseVect3Stub(stub) || stub } +const parseChunkKey = (chunkKey: ChunkKey) => { + const chunkId = new Vector3( + parseInt(chunkKey.split('_')[1] as string), + parseInt(chunkKey.split('_')[2] as string), + parseInt(chunkKey.split('_')[3] as string), + ) + return chunkId +} + +const serializeChunkId = (chunkId: Vector3) => { + return `chunk_${chunkId.x}_${chunkId.y}_${chunkId.z}` +} + +function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { + const chunk_ids = [] + for (let y = ymax; y >= ymin; y--) { + const chunk_coords = vect2ToVect3(patchId, y) + chunk_ids.push(chunk_coords) + } + return chunk_ids +} + +const getChunkBboxFromId = (chunkId: ChunkId, patchSize: number) => { + const bmin = chunkId.clone().multiplyScalar(patchSize) + const bmax = chunkId.clone().addScalar(1).multiplyScalar(patchSize) + const chunkBbox = new Box3(bmin, bmax) + chunkBbox.expandByScalar(1) + return chunkBbox +} + export { roundToDec, clamp, @@ -232,5 +269,10 @@ export { bboxContainsPointXZ, getPatchPoints, parseThreeStub, - asVect3, + vect2ToVect3, + vect3ToVect2, + parseChunkKey, + serializeChunkId, + genChunkIds, + getChunkBboxFromId } diff --git a/src/compute/WorldComputeApi.ts b/src/compute/WorldComputeApi.ts new file mode 100644 index 0000000..f0cf737 --- /dev/null +++ b/src/compute/WorldComputeApi.ts @@ -0,0 +1,123 @@ +import { Vector3 } from "three" +import { PatchKey } from "../common/types" +import { BlockData, BlocksPatch } from "../data/DataContainers" +import { BlockType, WorldCompute } from "../index" + +export enum ComputeApiCall { + PatchCompute = 'computePatch', + BlocksBatchCompute = 'computeBlocksBatch', + GroundBlockCompute = 'computeGroundBlock', + OvergroundBufferCompute = 'computeOvergroundBuffer', +} + +interface ComputeApiInterface { + computeBlocksBatch(blockPosBatch: Vector3[], params?: any): BlockData[] | Promise + // computePatch(patchKey: PatchKey): BlocksPatch | Promise + iterPatchCompute(patchKeysBatch: PatchKey[]): Generator | AsyncGenerator +} + +export class WorldComputeApi implements ComputeApiInterface { + static singleton: ComputeApiInterface + + pendingTask = false + startTime = Date.now() + elapsedTime = 0 + count = 0 + + static get instance() { + this.singleton = this.singleton || new WorldComputeApi() + return this.singleton + } + + static set worker(worker: Worker) { + this.singleton = new WorldComputeProxy(worker) + } + + computeBlocksBatch(blockPosBatch: Vector3[], params = { includeEntitiesBlocks: true }) { + const blocksBatch = blockPosBatch.map(({ x, z }) => { + const block_pos = new Vector3(x, 0, z) + const block = WorldCompute.computeGroundBlock(block_pos) + if (params.includeEntitiesBlocks) { + const blocksBuffer = WorldCompute.computeBlocksBuffer(block_pos) + const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) + if (lastBlockIndex >= 0) { + block.pos.y += lastBlockIndex + block.type = blocksBuffer[lastBlockIndex] as BlockType + } + } + return block + }) + return blocksBatch + } + + *iterPatchCompute(patchKeysBatch: PatchKey[]) { + for (const patchKey of patchKeysBatch) { + const patch = WorldCompute.computePatch(patchKey) + yield patch + } + } +} + +/** + * Proxying requests to worker instead of internal world compute + */ +export class WorldComputeProxy implements ComputeApiInterface { + worker: Worker + count = 0 + resolvers: Record = {} + + // eslint-disable-next-line no-undef + constructor(worker: Worker) { + // super() + this.worker = worker + this.worker.onmessage = ({ data }) => { + if (data.id !== undefined) { + this.resolvers[data.id]?.(data.data) + delete this.resolvers[data.id] + } else { + if (data) { + // data.kept?.length > 0 && PatchBlocksCache.cleanDeprecated(data.kept) + // data.created?.forEach(blocks_cache => { + // const blocks_patch = new PatchBlocksCache(blocks_cache) + // PatchBlocksCache.instances.push(blocks_patch) + // // patchRenderQueue.push(blocksPatch) + // }) + } + } + } + + this.worker.onerror = error => { + console.error(error) + } + + this.worker.onmessageerror = error => { + console.error(error) + } + } + + workerCall(apiName: ComputeApiCall, args: any[]) { + const id = this.count++ + this.worker.postMessage({ id, apiName, args }) + return new Promise(resolve => (this.resolvers[id] = resolve)) + } + + async computeBlocksBatch(blockPosBatch: Vector3[], params?: any) { + const blockStubs = await this.workerCall( + ComputeApiCall.BlocksBatchCompute, + [blockPosBatch, params], + ) + return blockStubs as BlockData[] + } + + async *iterPatchCompute(patchKeysBatch: PatchKey[]) { + for (const patchKey of patchKeysBatch) { + // const emptyPatch = new BlocksPatch(patchKey) + const patchStub = await this.workerCall( + ComputeApiCall.PatchCompute, + [patchKey] //[emptyPatch.bbox] + ) + const patch = BlocksPatch.fromStub(patchStub) + yield patch + } + } +} \ No newline at end of file diff --git a/src/compute/world-compute.ts b/src/compute/world-compute.ts new file mode 100644 index 0000000..fd48ba3 --- /dev/null +++ b/src/compute/world-compute.ts @@ -0,0 +1,154 @@ +import { Box3, Vector3 } from 'three' + +import { EntityType } from '../index' +import { Biome, BlockType } from '../procgen/Biome' +import { Heightmap } from '../procgen/Heightmap' +import { + EntitiesMap, + EntityData, + RepeatableEntitiesMap, +} from '../procgen/EntitiesMap' + +import { BlockData, BlocksContainer, BlocksPatch, EntityChunk } from '../data/DataContainers' +import { PatchKey } from '../common/types' + + + export const computePatch = (patchKey: PatchKey) => { + const patch = new BlocksPatch(patchKey) + genGroundBlocks(patch) + genEntitiesBlocks(patch) + return patch +} + +export const computeGroundBlock = (blockPos: Vector3) => { + const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) + const mainBiome = Biome.instance.getMainBiome(biomeContribs) + const rawVal = Heightmap.instance.getRawVal(blockPos) + const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) + 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 entityType = blockTypes.entities?.[0] as EntityType + // let offset = 0 + // if (lastBlock && entityType) { + + // } + // level += offset + const pos = blockPos.clone() + pos.y = level + const block: BlockData = { pos, type } + return block +} + +export const computeBlocksBuffer = (blockPos: Vector3) => { + let blocksBuffer: BlockType[] = [] + // query entities at current block + const entitiesIter = RepeatableEntitiesMap.instance.iterate(blockPos) + for (const entity of entitiesIter) { + // use global coords in case entity center is from adjacent patch + const entityPos = entity.bbox.getCenter(new Vector3()) + 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 + if (entityType) { + const entityLevel = Heightmap.instance.getGroundLevel(entityPos, rawVal) + entity.bbox.min.y = entityLevel + entity.bbox.max.y = entityLevel + 10 + entity.type = entityType + blocksBuffer = EntitiesMap.fillBlockBuffer( + blockPos, + entity, + blocksBuffer, + ) + } + } + return blocksBuffer +} + +const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { + const entityChunk: EntityChunk = { + bbox: new Box3(), + data: [], + } + const blocksIter = patch.iterOverBlocks(entity.bbox, true) + for (const block of blocksIter) { + const blocksBuffer = EntitiesMap.fillBlockBuffer(block.pos, entity, []) + patch.bbox.max.y = Math.max( + patch.bbox.max.y, + block.pos.y + blocksBuffer.length, + ) + const serialized = blocksBuffer + .reduce((str, val) => str + ',' + val, '') + .slice(1) + entityChunk.data.push(serialized) + entityChunk.bbox.expandByPoint(block.pos) + } + entityChunk.bbox = entity.bbox + return entityChunk +} + +const genEntitiesBlocks = (blocksContainer: BlocksContainer) => { + const entitiesIter = RepeatableEntitiesMap.instance.iterate(blocksContainer.bbox) + for (const entity of entitiesIter) { + // use global coords in case entity center is from adjacent patch + const entityPos = entity.bbox.getCenter(new Vector3()) + const biome = Biome.instance.getMainBiome(entityPos) + const rawVal = Heightmap.instance.getRawVal(entityPos) + const blockTypes = Biome.instance.getBlockType(rawVal, biome) + const entityType = blockTypes.entities?.[0] as EntityType + // const patchLocalBmin = new Vector3(min.x % patch.dimensions.x + min.x >= 0 ? 0 : patch.dimensions.x, + // 0, + // max.z % patch.dimensions.z + max.z >= 0 ? 0 : patch.dimensions.z) + if (entityType) { + const dims = entity.bbox.getSize(new Vector3()) + dims.y = 10 + const localBmin = entity.bbox.min.clone().sub(blocksContainer.bbox.min) + localBmin.y = Heightmap.instance.getGroundLevel(entityPos) + const localBmax = localBmin.clone().add(dims) + const localBbox = new Box3(localBmin, localBmax) + entity.bbox = localBbox + entity.type = entityType + const entityChunk = buildEntityChunk(blocksContainer, entity) + blocksContainer.entitiesChunks.push(entityChunk) + // let item: BlockIteratorRes = blocksIter.next() + } + } +} + +/** + * Fill container with ground blocks + */ +const genGroundBlocks = (blocksContainer: BlocksContainer) => { + const { min, max } = blocksContainer.bbox + // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z + // const prng = alea(patchId) + // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] + // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) + const blocksPatchIter = blocksContainer.iterOverBlocks(undefined, false, false) + min.y = 512 + max.y = 0 + let blockIndex = 0 + + for (const blockData of blocksPatchIter) { + const blockPos = blockData.pos + // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) + const block = computeGroundBlock(blockPos) + min.y = Math.min(min.y, block.pos.y) + max.y = Math.max(max.y, block.pos.y) + // blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) + blocksContainer.writeBlockAtIndex(blockIndex, block.pos.y, block.type) + blockIndex++ + } + blocksContainer.bbox.min = min + blocksContainer.bbox.max = max + blocksContainer.bbox.getSize(blocksContainer.dimensions) + // PatchBlocksCache.bbox.union(blocksContainer.bbox) + + // blocksContainer.state = PatchState.Filled + return blocksContainer +} \ No newline at end of file diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index 35a8fcf..c6f27df 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,7 +1,65 @@ +import { Vector3 } from "three"; +import { vect3ToVect2 } from "../common/utils"; import { BlockType } from "../procgen/Biome"; -import { BlocksContainer, PatchContainer } from "./Patches"; +import { BlocksContainer, PatchContainer } from "./DataContainers"; export class BoardContainer extends PatchContainer { + boardCenter + boardRadius + + constructor(center: Vector3, radius: number) { + super(); + this.boardRadius = radius + this.boardCenter = vect3ToVect2(center).floor() + const board_dims = new Vector3( + radius, + 0, + radius, + ).multiplyScalar(2) + this.bbox.setFromCenterAndSize( + center.clone().floor(), + board_dims, + ) + this.init(this.bbox) + } + + getMinMax() { + const { boardCenter, boardRadius } = this + let ymin = this.bbox.max.y + let ymax = this.bbox.min.y + this.availablePatches.forEach(patch => { + const blocks = patch.iterOverBlocks(this.bbox) + for (const block of blocks) { + // discard blocs not included in board shape + const dist = vect3ToVect2(block.pos) + .distanceTo(boardCenter) + if (dist <= boardRadius) { + const block_level = block.pos.y + ymin = Math.min(block_level, ymin) + ymax = Math.max(block_level, ymax) + } + } + }) + return { ymin, ymax } + } + + shapeBoard() { + const { boardCenter, boardRadius } = this + const { ymin, ymax } = this.getMinMax() + const avg = Math.round(ymin + (ymax - ymin) / 2) + this.availablePatches.forEach(patch => { + const blocks = patch.iterOverBlocks(this.bbox) + for (const block of blocks) { + // discard blocs not included in board shape + const dist = vect3ToVect2(block.pos) + .distanceTo(boardCenter,) + const y_diff = Math.abs(block.pos.y - avg) + if (dist <= boardRadius && y_diff <= 5) { + patch.writeBlockAtIndex(block.index, avg, block.type) + } + } + }) + } mergeBoardBlocks(blocksContainer: BlocksContainer) { // for each patch override with blocks from blocks container @@ -20,7 +78,7 @@ export class BoardContainer extends PatchContainer { }) } - interpolateBoardEdges(){ - + smoothEdges() { + } } \ No newline at end of file diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts new file mode 100644 index 0000000..4a21cdd --- /dev/null +++ b/src/data/CacheContainer.ts @@ -0,0 +1,163 @@ +import { Box3, Vector3 } from 'three' +import { PatchKey } from '../common/types' +import { WorldComputeApi } from '../index' + +import { + BlocksPatch, + EntityChunk, + PatchContainer, +} from './DataContainers' + +/** + * Blocks cache + */ +export class CacheContainer extends PatchContainer { + static singleton: CacheContainer + pendingRefresh = false + static cachePowRadius = 2 + static cacheSize = BlocksPatch.patchSize * 5 + // static worldApi = new WorldApi() + + // groundBlocks: Uint16Array = new Uint16Array(Math.pow(PatchBase.patchSize, 2)) + + entitiesChunks: EntityChunk[] = [] + + static get instance() { + this.singleton = this.singleton || new CacheContainer() + return this.singleton + } + + async populate(batch: PatchKey[], dryRun = false) { + if (!dryRun && batch.length > 0) { + this.pendingRefresh = true + const batchIter = WorldComputeApi.instance.iterPatchCompute(batch) + // populate cache without blocking execution + for await (const patch of batchIter) { + this.patchLookup[patch.key] = patch + this.bbox.union(patch.bbox) + } + this.pendingRefresh = false + } + return batch + } + + /** + * + * @param center + * @param dryRun + * @returns true if cache was update, false otherwise + */ + async refresh( + bbox: Box3, + dryRun = false + ) { + const changes: any = { + count: 0, + batch: [] + } + if (!this.pendingRefresh) { + const emptyContainer = new PatchContainer() + emptyContainer.init(bbox) + const diff = emptyContainer.diffWithPatchContainer(CacheContainer.instance) + changes.count = Object.keys(diff).length + + // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) + if (changes.count) { + // backup patches that will remain in cache + const backup = this.availablePatches.filter(patch => patch) + // reinit cache + super.init(bbox) + // restore remaining patches backup + this.populateFromExisting(backup) + // return patch keys needing to be retrieved + changes.batch = dryRun ? this.missingPatchKeys : await this.populate(this.missingPatchKeys) + } + } + return changes + } + + getPatches(inputBbox: Box3) { + const bbox = inputBbox.clone() + bbox.min.y = 0 + bbox.max.y = 512 + const res = this.availablePatches.filter(patch => + patch.bbox.intersectsBox(bbox), + ) + return res + } + + getNearPatches(patch: BlocksPatch) { + const dim = patch.dimensions + const patchCenter = patch.bbox.getCenter(new Vector3()) + const minX = patchCenter.clone().add(new Vector3(-dim.x, 0, 0)) + const maxX = patchCenter.clone().add(new Vector3(dim.x, 0, 0)) + const minZ = patchCenter.clone().add(new Vector3(0, 0, -dim.z)) + const maxZ = patchCenter.clone().add(new Vector3(0, 0, dim.z)) + const minXminZ = patchCenter.clone().add(new Vector3(-dim.x, 0, -dim.z)) + const minXmaxZ = patchCenter.clone().add(new Vector3(-dim.x, 0, dim.z)) + const maxXminZ = patchCenter.clone().add(new Vector3(dim.x, 0, -dim.z)) + const maxXmaxZ = patchCenter.clone().add(new Vector3(dim.x, 0, dim.z)) + const neighboursCenters = [ + minX, + maxX, + minZ, + maxZ, + minXminZ, + minXmaxZ, + maxXminZ, + maxXmaxZ, + ] + const patchNeighbours: BlocksPatch[] = neighboursCenters + .map(patchCenter => this.findPatch(patchCenter)) + .filter(patch => patch) as BlocksPatch[] + return patchNeighbours + } + + // getGroundBlock(globalPos: Vector3) { + // const { bbox } = this + // let blockRes + // globalPos.y = bbox.getCenter(new Vector3()).y + // if (bbox.containsPoint(globalPos)) { + // const patch = this.findPatch(globalPos) + // if (patch) { + // const localPos = globalPos.clone().sub(patch.bbox.min) + // blockRes = patch.getBlock(localPos) as BlockData + // } + // } else { + // const batchRes = WorldComputeApi.instance.computeBlocksBatch([globalPos]) + // const blockRes = batchRes instanceof Promise ? batchRes.then(batchRes => batchRes[0]) : batchRes[0] + // if (!blockRes) { + // console.log(blockRes) + // } + // } + // return blockRes + // } + + // async getUpperBlock(globalPos: Vector3) { + // const block = await this.getGroundBlock(globalPos) + // if (block) { + // const blocksBuffer = (await WorldApi.instance.call( + // WorldApiName.OvergroundBufferCompute, + // [block.pos], + // )) as BlockType[] + // const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) + // if (lastBlockIndex >= 0) { + // block.pos.y += lastBlockIndex + // block.type = blocksBuffer[lastBlockIndex] as BlockType + // } + // } + // return block + // } + + // setBlock(globalPos: Vector3, block: BlockData) { + // // find patch containing point in cache + // const patch = this.findPatch(globalPos) + // if (patch) { + // const localPos = globalPos.clone().sub(patch.bbox.min) + // patch.setBlock(localPos, block.type) + // } else { + // console.log(globalPos) + // } + // return block + // } +} diff --git a/src/data/Patches.ts b/src/data/DataContainers.ts similarity index 85% rename from src/data/Patches.ts rename to src/data/DataContainers.ts index 73322a1..2229b37 100644 --- a/src/data/Patches.ts +++ b/src/data/DataContainers.ts @@ -1,8 +1,10 @@ import { Box3, Vector2, Vector3 } from 'three' -import { asVect3, parseThreeStub } from '../common/utils' - +import { PatchKey } from '../common/types' +import { genChunkIds, parseThreeStub, vect2ToVect3 } from '../common/utils' +import { ChunkTools } from '../index' import { BlockType } from '../procgen/Biome' + export type BlockData = { pos: Vector3 type: BlockType @@ -33,6 +35,10 @@ export type PatchStub = { export type BlockIteratorRes = IteratorResult +/** + * GenericBlocksContainer + * multi purpose blocks container + */ export class BlocksContainer { bbox: Box3 dimensions = new Vector3() @@ -169,7 +175,19 @@ export class BlocksContainer { } } - static fromStub(stub: BlocksContainer) { + containsBlock(blockPos: Vector3) { + return ( + blockPos.x >= this.bbox.min.x && + blockPos.z >= this.bbox.min.z && + blockPos.x < this.bbox.max.x && + blockPos.z < this.bbox.max.z) + } + + toChunk() { + return ChunkTools.makeChunkFromBox(this, this.bbox) + } + + static fromStub(stub: any) { const { groundBlocks, entitiesChunks } = stub const blocksContainer = new BlocksContainer(parseThreeStub(stub.bbox)) blocksContainer.groundBlocks = groundBlocks @@ -182,6 +200,9 @@ export class BlocksContainer { } +/** + * Patch + */ export class BlocksPatch extends BlocksContainer { // eslint-disable-next-line no-use-before-define // static cache: BlocksPatch[] = [] @@ -205,7 +226,7 @@ export class BlocksPatch extends BlocksContainer { return duplicate } - static override fromStub(patchStub: BlocksPatch) { + static override fromStub(patchStub: any) { const { groundBlocks, entitiesChunks } = patchStub const bbox = parseThreeStub(patchStub.bbox) const patchKey = patchStub.key || this.computePatchKey(bbox) @@ -262,27 +283,39 @@ export class BlocksPatch extends BlocksContainer { const patchKey = `patch_${x}_${y}` return patchKey } + + toChunks(yMin: number, yMax: number) { + const chunkIds = genChunkIds( + this.coords, + yMin, + yMax, + ) + const chunks = chunkIds.map(chunkId => + ChunkTools.makeChunkFromId(this, chunkId), + ) + return chunks + } } export class PatchContainer { - bbox: Box3 - patchRange: Box3 + bbox: Box3 = new Box3() patchLookup: Record = {} - constructor(bbox: Box3) { - this.bbox = bbox - const rangeMin = BlocksPatch.asPatchCoords(bbox.min) - const rangeMax = BlocksPatch.asPatchCoords(bbox.max).addScalar(1) - this.patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) - this.init() + get patchIdsRange() { + const rangeMin = BlocksPatch.asPatchCoords(this.bbox.min) + const rangeMax = BlocksPatch.asPatchCoords(this.bbox.max).addScalar(1) + const patchIdsRange = new Box3(vect2ToVect3(rangeMin), vect2ToVect3(rangeMax)) + return patchIdsRange } - init() { + init(bbox: Box3) { + this.bbox = bbox + this.patchLookup = {} // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) // const range = BlocksPatch.asPatchCoords(halfDimensions) // const center = this.bbox.getCenter(new Vector3()) // const origin = BlocksPatch.asPatchCoords(center) - const { min, max } = this.patchRange + const { min, max } = this.patchIdsRange for (let x = min.x; x < max.x; x++) { for (let z = min.z; z < max.z; z++) { const patchKey = 'patch_' + x + '_' + z; @@ -296,7 +329,7 @@ export class PatchContainer { } get missingPatchKeys() { - return Object.keys(this.patchLookup).filter(key => !this.patchLookup[key]) + return Object.keys(this.patchLookup).filter(key => !this.patchLookup[key]) as PatchKey[] } get count() { @@ -312,7 +345,7 @@ export class PatchContainer { // this.availablePatches.forEach(patch=>patch.iterOverBlocks) // } - fillFromPatches(patches: BlocksPatch[], cloneObjects = false) { + populateFromExisting(patches: BlocksPatch[], cloneObjects = false) { const { min, max } = this.bbox patches.filter(patch => this.patchLookup[patch.key] !== undefined) .forEach(patch => { @@ -351,4 +384,20 @@ export class PatchContainer { .forEach(patchKey => patchKeysDiff[patchKey] = false) return patchKeysDiff } + + toChunks(yMin: number, yMax: number) { + const chunksExport = this.availablePatches.map(patch => patch.toChunks(yMin, yMax)).flat() + return chunksExport + } + + findPatch(blockPos: Vector3) { + // const point = new Vector3( + // inputPoint.x, + // 0, + // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, + // ) + + const res = this.availablePatches.find(patch => patch.containsBlock(blockPos)) + return res + } } diff --git a/src/index.ts b/src/index.ts index 70b6056..3273f6d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,14 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { BlocksContainer, BlocksPatch, PatchContainer } from './data/Patches' +export { BlocksContainer, BlocksPatch, PatchContainer } from './data/DataContainers' export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' -export { WorldApi, WorldWorkerApi, WorldApiName } from './world/WorldApi' -export { WorldCache } from './world/WorldCache' -export { WorldCompute } from './world/WorldCompute' +export { CacheContainer } from './data/CacheContainer' +export { WorldComputeApi } from './compute/WorldComputeApi' +export * as WorldCompute from './compute/world-compute' export * as WorldUtils from './common/utils' export * as ChunkTools from './utils/chunk_tools' // export * as BoardUtils from './utils/BoardUtils' diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index 35819e7..26ae265 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -4,7 +4,7 @@ import { Box3, Vector2, Vector3 } from 'three' import { TreeGenerators } from '../tools/TreeGenerator' import { EntityType } from '../index' -import { BlocksPatch } from '../world/WorldData' +import { BlocksPatch } from '../data/DataContainers' import { ProcLayer } from './ProcLayer' import { BlockType } from './Biome' diff --git a/src/utils/chunk_tools.ts b/src/utils/chunk_tools.ts index 0bb5543..477527a 100644 --- a/src/utils/chunk_tools.ts +++ b/src/utils/chunk_tools.ts @@ -1,9 +1,10 @@ import { Box3, MathUtils, Vector3 } from "three" +import { ChunkId, WorldChunk } from "../common/types" +import { getChunkBboxFromId, serializeChunkId } from "../common/utils" import { BlocksContainer, BlocksPatch, BlockType, - WorldUtils } from '../index' const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND @@ -19,7 +20,7 @@ const writeChunkBlocks = ( chunkBbox: Box3, blockLocalPos: Vector3, groundType: BlockType, - bufferOver = [], + bufferOver: any[] = [], ) => { const chunk_size = Math.round(Math.pow(chunkData.length, 1 / 3)) @@ -92,9 +93,9 @@ const fillEntitiesData = (blocksContainer: BlocksContainer, chunkData: Uint16Arr for (const block of blocks_iter) { const bufferStr = entity_chunk.data[chunk_index] const buffer = - bufferStr.length > 0 && + bufferStr && bufferStr.split(',').map(char => parseInt(char)) - if (buffer.length > 0) { + if (buffer && block.localPos) { block.buffer = buffer block.localPos.x += 1 block.localPos.z += 1 @@ -113,25 +114,21 @@ const fillEntitiesData = (blocksContainer: BlocksContainer, chunkData: Uint16Arr return written_blocks_count } -export const getChunkBbox = (chunk_id: Vector3) => { - const bmin = chunk_id.clone().multiplyScalar(BlocksPatch.patchSize) - const bmax = chunk_id.clone().addScalar(1).multiplyScalar(BlocksPatch.patchSize) - const chunkBbox = new Box3(bmin, bmax) - chunkBbox.expandByScalar(1) - return chunkBbox -} - -export function makeChunk(blocksContainer: BlocksContainer, chunk_id: Vector3) { - const chunkBox = getChunkBbox(chunk_id) - const final_chunk = makeCustomChunk(blocksContainer, chunkBox) - final_chunk.id = chunk_id - return final_chunk +export function makeChunkFromId(blocksContainer: BlocksContainer, chunkId: ChunkId) { + const chunkBox = getChunkBboxFromId(chunkId, BlocksPatch.patchSize) + const chunk = makeChunkFromBox(blocksContainer, chunkBox) + const regularChunk: WorldChunk = { + key: serializeChunkId(chunkId), + data: chunk.data + } + return regularChunk } -export function makeCustomChunk(blocksContainer: BlocksContainer, chunkBox: Box3) { - const chunk_dims = chunkBox.getSize(new Vector3()) - const chunkData = new Uint16Array(chunk_dims.x * chunk_dims.y * chunk_dims.z) - let total_written_blocks_count = 0 +export function makeChunkFromBox(blocksContainer: BlocksContainer, _chunkBox?: Box3) { + const chunkBox = _chunkBox || blocksContainer.bbox + const chunkDims = chunkBox.getSize(new Vector3()) + const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) + let totalWrittenBlocks = 0 // const debug_mode = true // const is_edge = (row, col, h, patch_size) => @@ -151,13 +148,13 @@ export function makeCustomChunk(blocksContainer: BlocksContainer, chunkBox: Box3 // multi-pass chunk filling if (blocksContainer) { // ground pass - total_written_blocks_count += fillGroundData( + totalWrittenBlocks += fillGroundData( blocksContainer, chunkData, chunkBox, ) // overground entities pass - total_written_blocks_count += fillEntitiesData( + totalWrittenBlocks += fillEntitiesData( blocksContainer, chunkData, chunkBox, @@ -166,24 +163,13 @@ export function makeCustomChunk(blocksContainer: BlocksContainer, chunkBox: Box3 // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) // const dimensions = new Vector3(size, size, size) const chunk = { - data: chunkData, - size: chunk_dims, - isEmpty: total_written_blocks_count === 0, + bbox: chunkBox, + data: totalWrittenBlocks ? chunkData : null, + // isEmpty: totalWrittenBlocks === 0, } return chunk } -export function genChunkIds(patch: BlocksPatch, ymin: number, ymax: number) { - const chunk_ids = [] - if (patch) { - for (let y = ymax; y >= ymin; y--) { - const chunk_coords = WorldUtils.asVect3(patch.coords, y) - chunk_ids.push(chunk_coords) - } - } - return chunk_ids -} - // const plateau_ground_pass = (blocksContainer, chunk) => { // const patch_center = blocksContainer.bbox.getCenter(new Vector3()) // patch.bbox.min.y // const plateau_height = Math.floor(patch_center.y) diff --git a/src/utils/plateau_legacy.ts b/src/utils/plateau_legacy.ts index b75fd3b..dc9eb70 100644 --- a/src/utils/plateau_legacy.ts +++ b/src/utils/plateau_legacy.ts @@ -2,8 +2,8 @@ // import { voxelmapDataPacking, type IVoxelMap } from '../i-voxelmap'; import { Box3, Vector3, Vector3Like } from 'three' -import { ChunkTools, WorldApi, WorldApiName } from '../index' -import { BlocksContainer } from '../data/Patches' +import { ChunkTools } from '../index' +import { BlocksContainer } from '../data/DataContainers' enum EPlateauSquareType { FLAT = 0, @@ -101,12 +101,9 @@ async function computePlateau( const dataFromWorld = new Vector3().copy(originWorld).subScalar(dataMargin) const dataToWorld = new Vector3().copy(originWorld).addScalar(dataMargin) const dataBbox = new Box3(dataFromWorld, dataToWorld) - const containerStub = await WorldApi.instance.call( - WorldApiName.PatchCompute, - [dataBbox]//[patchKey], - ) + const containerStub: any = null //await WorldCompute.instance.iterPatchCompute() const blocksContainer = BlocksContainer.fromStub(containerStub) - const chunk = ChunkTools.makeCustomChunk(blocksContainer, dataBbox) + const chunk = ChunkTools.makeChunkFromBox(blocksContainer, dataBbox) const data = chunk//await map.getLocalMapData(dataFromWorld, dataToWorld) const dataSize = dataToWorld.clone().sub(dataFromWorld) diff --git a/src/world/WorldApi.ts b/src/world/WorldApi.ts deleted file mode 100644 index 4acebb6..0000000 --- a/src/world/WorldApi.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { WorldCompute } from './WorldCompute' - -export enum WorldApiName { - PatchCompute = 'computePatch', - BlocksBatchCompute = 'computeBlocksBatch', - GroundBlockCompute = 'computeGroundBlock', - OvergroundBufferCompute = 'computeOvergroundBuffer', -} - -/** - * Frontend to access world api defaulting to using local world instance - * can be overriden to provide custom implementation - */ -export class WorldApi { - // eslint-disable-next-line no-use-before-define - static usedApi: WorldApi - - static get instance() { - WorldApi.usedApi = WorldApi.usedApi || new WorldApi() - return WorldApi.usedApi - } - - // call(api: WorldApiName, args: any[]): T | Promise - - async call(apiName: WorldApiName, args: any) { - return await WorldCompute[apiName](args[0]) - } -} - -/** - * World api provider to access worker instance - */ -export class WorldWorkerApi extends WorldApi { - // static usedApi: WorldWorkerApi - // eslint-disable-next-line no-undef - worker: Worker - count = 0 - resolvers: Record = {} - - // eslint-disable-next-line no-undef - constructor(worker: Worker) { - super() - this.worker = worker - this.worker.onmessage = ({ data }) => { - if (data.id !== undefined) { - this.resolvers[data.id]?.(data.data) - delete this.resolvers[data.id] - } else { - if (data) { - // data.kept?.length > 0 && PatchBlocksCache.cleanDeprecated(data.kept) - // data.created?.forEach(blocks_cache => { - // const blocks_patch = new PatchBlocksCache(blocks_cache) - // PatchBlocksCache.instances.push(blocks_patch) - // // patchRenderQueue.push(blocksPatch) - // }) - } - } - } - - this.worker.onerror = error => { - console.error(error) - } - - this.worker.onmessageerror = error => { - console.error(error) - } - } - - override call(apiName: WorldApiName, args: any[]) { - const id = this.count++ - this.worker.postMessage({ id, apiName, args }) - return new Promise(resolve => (this.resolvers[id] = resolve)) - } - - // static get instance() { - // WorldWorkerApi.usedApi = - // WorldWorkerApi.usedApi || new WorldWorkerApi() - // return WorldWorkerApi.usedApi - // } -} diff --git a/src/world/WorldCache.ts b/src/world/WorldCache.ts deleted file mode 100644 index e3bdc74..0000000 --- a/src/world/WorldCache.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Box3, Vector2, Vector3 } from 'three' -import { BlockType } from '../index' - -import { WorldApi, WorldApiName } from './WorldApi' -import { - BlockData, - BlocksPatch, - BlockStub, - EntityChunk, - PatchContainer, - PatchStub, -} from '../data/Patches' - -/** - * Blocks cache - */ -export class WorldCache { - static bbox = new Box3() // global cache extent - // static lastCacheBox = new Box3() - static pendingRefresh = false - static cachePowRadius = 2 - static cacheSize = BlocksPatch.patchSize * 5 - static patchContainer = new PatchContainer(new Box3()) - // static worldApi = new WorldApi() - - // groundBlocks: Uint16Array = new Uint16Array(Math.pow(PatchBase.patchSize, 2)) - - entitiesChunks: EntityChunk[] = [] - - static async *processBatchItems(batchContent: string[]) { - for (const patchKey of batchContent) { - const emptyPatch = new BlocksPatch(patchKey) - const patchStub = await WorldApi.instance.call( - WorldApiName.PatchCompute, - [emptyPatch.bbox]//[patchKey], - ) - yield patchStub as PatchStub - } - } - - static async processBlocksBatch(batchContent: Vector3[]) { - const batchRes = await WorldApi.instance.call( - WorldApiName.BlocksBatchCompute, - [batchContent], - ) - return batchRes - } - - static async populate(patchContainer: PatchContainer, dryRun = false) { - const batchContent = patchContainer.missingPatchKeys - if (!dryRun && batchContent.length > 0) { - this.pendingRefresh = true - - const batchIter = WorldCache.processBatchItems(batchContent) - // populate cache - for await (const patchStub of batchIter) { - const patch = BlocksPatch.fromStub(patchStub) - patchContainer.patchLookup[patch.key] = patch - patchContainer.bbox.union(patch.bbox) - } - this.pendingRefresh = false - } - return batchContent - } - - /** - * - * @param center - * @param dryRun - * @returns true if cache was update, false otherwise - */ - static async refresh( - bbox: Box3, - dryRun = false - ) { - const changes: any = { - count: 0, - batch: [] - } - if (!this.pendingRefresh) { - const patchContainer = new PatchContainer(bbox) - const patchDiff = patchContainer.diffWithPatchContainer(this.patchContainer) - changes.count = Object.keys(patchDiff).length - // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) - if (changes.count) { - patchContainer.fillFromPatches(this.patchContainer.availablePatches) - this.patchContainer = patchContainer - changes.batch = await this.populate(patchContainer, dryRun) - } - } - return changes - } - - static getPatch(inputPoint: Vector2 | Vector3) { - const point = new Vector3( - inputPoint.x, - 0, - inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, - ) - - const res = this.patchContainer.availablePatches.find( - patch => - point.x >= patch.bbox.min.x && - point.z >= patch.bbox.min.z && - point.x < patch.bbox.max.x && - point.z < patch.bbox.max.z, - ) - return res - } - - static getPatches(inputBbox: Box3) { - const bbox = inputBbox.clone() - bbox.min.y = 0 - bbox.max.y = 512 - const res = this.patchContainer.availablePatches.filter(patch => - patch.bbox.intersectsBox(bbox), - ) - return res - } - - getNearPatches(patch: BlocksPatch) { - const dim = patch.dimensions - const patchCenter = patch.bbox.getCenter(new Vector3()) - const minX = patchCenter.clone().add(new Vector3(-dim.x, 0, 0)) - const maxX = patchCenter.clone().add(new Vector3(dim.x, 0, 0)) - const minZ = patchCenter.clone().add(new Vector3(0, 0, -dim.z)) - const maxZ = patchCenter.clone().add(new Vector3(0, 0, dim.z)) - const minXminZ = patchCenter.clone().add(new Vector3(-dim.x, 0, -dim.z)) - const minXmaxZ = patchCenter.clone().add(new Vector3(-dim.x, 0, dim.z)) - const maxXminZ = patchCenter.clone().add(new Vector3(dim.x, 0, -dim.z)) - const maxXmaxZ = patchCenter.clone().add(new Vector3(dim.x, 0, dim.z)) - const neighboursCenters = [ - minX, - maxX, - minZ, - maxZ, - minXminZ, - minXmaxZ, - maxXminZ, - maxXmaxZ, - ] - const patchNeighbours: BlocksPatch[] = neighboursCenters - .map(patchCenter => WorldCache.getPatch(patchCenter)) - .filter(patch => patch) as BlocksPatch[] - return patchNeighbours - } - - static getGroundBlock(globalPos: Vector3) { - const { bbox } = this.patchContainer - let res - globalPos.y = bbox.getCenter(new Vector3()).y - if (bbox.containsPoint(globalPos)) { - const patch = WorldCache.getPatch(globalPos) - if (patch) { - const localPos = globalPos.clone().sub(patch.bbox.min) - res = patch.getBlock(localPos) as BlockData - } - } else { - res = WorldApi.instance - .call(WorldApiName.GroundBlockCompute, [globalPos]) - .then(blockStub => { - const block = { - pos: new Vector3( - globalPos.x, - (blockStub as BlockStub).level, - globalPos.z, - ), - type: (blockStub as BlockStub).type, - } - return block - }) - if (!res) { - console.log(res) - } - } - return res - } - - static async getTopLevelBlock(globalPos: Vector3) { - const block = await WorldCache.getGroundBlock(globalPos) - if (block) { - const blocksBuffer = (await WorldApi.instance.call( - WorldApiName.OvergroundBufferCompute, - [block.pos], - )) as BlockType[] - const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) - if (lastBlockIndex >= 0) { - block.pos.y += lastBlockIndex - block.type = blocksBuffer[lastBlockIndex] as BlockType - } - } - return block - } - - static setBlock(globalPos: Vector3, block: BlockData) { - // find patch containing point in cache - const patch = this.getPatch(globalPos) - if (patch) { - const localPos = globalPos.clone().sub(patch.bbox.min) - patch.setBlock(localPos, block.type) - } else { - console.log(globalPos) - } - return block - } - - static buildPlateau(patchKeys: string[]) { - const patches = this.patchContainer.availablePatches - const bbox = patches.reduce( - (bbox, patch) => bbox.union(patch?.bbox || new Box3()), - new Box3(), - ) - console.log(patchKeys) - } -} diff --git a/src/world/WorldCompute.ts b/src/world/WorldCompute.ts deleted file mode 100644 index 5b4d69c..0000000 --- a/src/world/WorldCompute.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Box3, Vector3 } from 'three' - -import { EntityType } from '../index' -import { Biome, BlockType } from '../procgen/Biome' -import { Heightmap } from '../procgen/Heightmap' -import { - EntitiesMap, - EntityData, - RepeatableEntitiesMap, -} from '../procgen/EntitiesMap' - -import { BlocksContainer, BlocksPatch, EntityChunk } from '../data/Patches' - -export class WorldCompute { - static pendingTask = false - startTime = Date.now() - elapsedTime = 0 - count = 0 - - // patch keys as input - inputKeys: string[] = [] - // patch stubs as output - outputStubs: BlocksPatch[] = [] - - constructor(inputKeys: string[]) { - this.inputKeys = inputKeys - } - - static computeGroundBlock(blockPos: Vector3) { - const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) - const mainBiome = Biome.instance.getMainBiome(biomeContribs) - const rawVal = Heightmap.instance.getRawVal(blockPos) - const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) - 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 entityType = blockTypes.entities?.[0] as EntityType - // let offset = 0 - // if (lastBlock && entityType) { - - // } - // level += offset - const block = { level, type } - return block - } - - static computeOvergroundBuffer(blockPos: Vector3) { - let blocksBuffer: BlockType[] = [] - // query entities at current block - const entitiesIter = RepeatableEntitiesMap.instance.iterate(blockPos) - for (const entity of entitiesIter) { - // use global coords in case entity center is from adjacent patch - const entityPos = entity.bbox.getCenter(new Vector3()) - 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 - if (entityType) { - const entityLevel = Heightmap.instance.getGroundLevel(entityPos, rawVal) - entity.bbox.min.y = entityLevel - entity.bbox.max.y = entityLevel + 10 - entity.type = entityType - blocksBuffer = EntitiesMap.fillBlockBuffer( - blockPos, - entity, - blocksBuffer, - ) - } - } - return blocksBuffer - } - - static computeBlocksBatch(batchContent: [], includeEntities = true) { - const batchRes = batchContent.map(({ x, z }) => { - const block_pos = new Vector3(x, 0, z) - const block = WorldCompute.computeGroundBlock(block_pos) - if (includeEntities) { - const blocksBuffer = WorldCompute.computeOvergroundBuffer(block_pos) - const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) - if (lastBlockIndex >= 0) { - block.level += lastBlockIndex - block.type = blocksBuffer[lastBlockIndex] as BlockType - } - } - return block - }) - return batchRes - } - - static computePatch(bbox: Box3) { - const patch = new BlocksContainer(bbox) - WorldCompute.genGroundBlocks(patch) - WorldCompute.genEntitiesBlocks(patch) - return patch - } - - static buildEntityChunk(patch: BlocksContainer, entity: EntityData) { - const entityChunk: EntityChunk = { - bbox: new Box3(), - data: [], - } - const blocksIter = patch.iterOverBlocks(entity.bbox, true) - for (const block of blocksIter) { - const blocksBuffer = EntitiesMap.fillBlockBuffer(block.pos, entity, []) - patch.bbox.max.y = Math.max( - patch.bbox.max.y, - block.pos.y + blocksBuffer.length, - ) - const serialized = blocksBuffer - .reduce((str, val) => str + ',' + val, '') - .slice(1) - entityChunk.data.push(serialized) - entityChunk.bbox.expandByPoint(block.pos) - } - entityChunk.bbox = entity.bbox - return entityChunk - } - - static genEntitiesBlocks(blocksContainer: BlocksContainer) { - const entitiesIter = RepeatableEntitiesMap.instance.iterate(blocksContainer.bbox) - for (const entity of entitiesIter) { - // use global coords in case entity center is from adjacent patch - const entityPos = entity.bbox.getCenter(new Vector3()) - const biome = Biome.instance.getMainBiome(entityPos) - const rawVal = Heightmap.instance.getRawVal(entityPos) - const blockTypes = Biome.instance.getBlockType(rawVal, biome) - const entityType = blockTypes.entities?.[0] as EntityType - // const patchLocalBmin = new Vector3(min.x % patch.dimensions.x + min.x >= 0 ? 0 : patch.dimensions.x, - // 0, - // max.z % patch.dimensions.z + max.z >= 0 ? 0 : patch.dimensions.z) - if (entityType) { - const dims = entity.bbox.getSize(new Vector3()) - dims.y = 10 - const localBmin = entity.bbox.min.clone().sub(blocksContainer.bbox.min) - localBmin.y = Heightmap.instance.getGroundLevel(entityPos) - const localBmax = localBmin.clone().add(dims) - const localBbox = new Box3(localBmin, localBmax) - entity.bbox = localBbox - entity.type = entityType - const entityChunk = WorldCompute.buildEntityChunk(blocksContainer, entity) - blocksContainer.entitiesChunks.push(entityChunk) - // let item: BlockIteratorRes = blocksIter.next() - } - } - } - - /** - * Fill container with ground blocks - */ - static genGroundBlocks(blocksContainer: BlocksContainer) { - const { min, max } = blocksContainer.bbox - // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z - // const prng = alea(patchId) - // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] - // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const blocksPatchIter = blocksContainer.iterOverBlocks(undefined, false, false) - min.y = 512 - max.y = 0 - let blockIndex = 0 - - for (const blockData of blocksPatchIter) { - const blockPos = blockData.pos - // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) - const block = this.computeGroundBlock(blockPos) - min.y = Math.min(min.y, block.level) - max.y = Math.max(max.y, block.level) - // blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) - blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) - blockIndex++ - } - blocksContainer.bbox.min = min - blocksContainer.bbox.max = max - blocksContainer.bbox.getSize(blocksContainer.dimensions) - // PatchBlocksCache.bbox.union(blocksContainer.bbox) - - // blocksContainer.state = PatchState.Filled - return blocksContainer - } -} From 3506442c9a6e9e246a57952195744b0ea320ab45 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 14 Aug 2024 09:06:18 +0000 Subject: [PATCH 06/45] fix: formatting + include trailing changes --- src/common/types.ts | 2 +- src/common/utils.ts | 56 +++- src/compute/WorldComputeApi.ts | 48 ++-- src/compute/world-compute.ts | 51 +++- src/data/BoardContainer.ts | 144 +++++----- src/data/CacheContainer.ts | 36 +-- src/data/DataContainers.ts | 176 ++++++------ src/index.ts | 6 +- src/utils/chunk_tools.ts | 62 ++-- src/utils/plateau_legacy.ts | 505 ++++++++++++++++----------------- 10 files changed, 577 insertions(+), 509 deletions(-) diff --git a/src/common/types.ts b/src/common/types.ts index 0acb930..19d18d6 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -114,6 +114,6 @@ export type ChunkKey = string export type ChunkId = Vector3 export type WorldChunk = { - key: ChunkKey, + key: ChunkKey data: Uint16Array | null } diff --git a/src/common/utils.ts b/src/common/utils.ts index f296068..7e351cc 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -8,6 +8,7 @@ import { MappingRange, MappingRanges, PatchId, + PatchKey, } from './types' // Clamp number between two values: @@ -223,7 +224,52 @@ const isVect3Stub = (stub: Vector3Like) => { } const parseThreeStub = (stub: any) => { - return parseBox3Stub(stub) || parseVect3Stub(stub) || stub + return stub ? parseBox3Stub(stub) || parseVect3Stub(stub) || stub : stub +} + +const parsePatchKey = (patchKey: PatchKey) => { + const patchId = new Vector2( + parseInt(patchKey.split('_')[1] as string), + parseInt(patchKey.split('_')[2] as string), + ) + return patchId +} + +const convertPosToPatchId = (position: Vector3, patchSize: number) => { + const orig_x = Math.floor(position.x / patchSize) + const orig_z = Math.floor(position.z / patchSize) + const patchCoords = new Vector2(orig_x, orig_z) + return patchCoords +} + +const computePatchKey = ( + patchId: Box3 | Vector3 | Vector2, + patchSize: number, +) => { + const inputCopy: Vector3 | Box3 = + patchId instanceof Vector2 + ? new Vector3(patchId.x, 0, patchId.y) + : patchId.clone() + const point = + inputCopy instanceof Box3 + ? (inputCopy as Box3).getCenter(new Vector3()) + : (inputCopy as Vector3).clone() + + const patchOrigin = convertPosToPatchId(point, patchSize) + const { x, y } = patchOrigin + const patchKey = `patch_${x}_${y}` + return patchKey +} + +const getBboxFromPatchKey = (patchKey: string, patchSize: number) => { + const patchCoords = parsePatchKey(patchKey) + const bmin = vect2ToVect3(patchCoords.clone().multiplyScalar(patchSize)) + const bmax = vect2ToVect3( + patchCoords.clone().addScalar(1).multiplyScalar(patchSize), + ) + bmax.y = 512 + const bbox = new Box3(bmin, bmax) + return bbox } const parseChunkKey = (chunkKey: ChunkKey) => { @@ -248,7 +294,7 @@ function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { return chunk_ids } -const getChunkBboxFromId = (chunkId: ChunkId, patchSize: number) => { +const getBboxFromChunkId = (chunkId: ChunkId, patchSize: number) => { const bmin = chunkId.clone().multiplyScalar(patchSize) const bmax = chunkId.clone().addScalar(1).multiplyScalar(patchSize) const chunkBbox = new Box3(bmin, bmax) @@ -271,8 +317,12 @@ export { parseThreeStub, vect2ToVect3, vect3ToVect2, + parsePatchKey, + convertPosToPatchId, + computePatchKey, + getBboxFromPatchKey, parseChunkKey, serializeChunkId, + getBboxFromChunkId, genChunkIds, - getChunkBboxFromId } diff --git a/src/compute/WorldComputeApi.ts b/src/compute/WorldComputeApi.ts index f0cf737..218c21f 100644 --- a/src/compute/WorldComputeApi.ts +++ b/src/compute/WorldComputeApi.ts @@ -1,7 +1,8 @@ -import { Vector3 } from "three" -import { PatchKey } from "../common/types" -import { BlockData, BlocksPatch } from "../data/DataContainers" -import { BlockType, WorldCompute } from "../index" +import { Vector3 } from 'three' + +import { PatchKey } from '../common/types' +import { BlockData, BlocksPatch } from '../data/DataContainers' +import { WorldCompute } from '../index' export enum ComputeApiCall { PatchCompute = 'computePatch', @@ -11,9 +12,16 @@ export enum ComputeApiCall { } interface ComputeApiInterface { - computeBlocksBatch(blockPosBatch: Vector3[], params?: any): BlockData[] | Promise + computeBlocksBatch( + blockPosBatch: Vector3[], + params?: any, + ): BlockData[] | Promise // computePatch(patchKey: PatchKey): BlocksPatch | Promise - iterPatchCompute(patchKeysBatch: PatchKey[]): Generator | AsyncGenerator + iterPatchCompute( + patchKeysBatch: PatchKey[], + ): + | Generator + | AsyncGenerator } export class WorldComputeApi implements ComputeApiInterface { @@ -29,25 +37,16 @@ export class WorldComputeApi implements ComputeApiInterface { return this.singleton } - static set worker(worker: Worker) { + // eslint-disable-next-line no-undef + static useWorker(worker: Worker) { this.singleton = new WorldComputeProxy(worker) } - computeBlocksBatch(blockPosBatch: Vector3[], params = { includeEntitiesBlocks: true }) { - const blocksBatch = blockPosBatch.map(({ x, z }) => { - const block_pos = new Vector3(x, 0, z) - const block = WorldCompute.computeGroundBlock(block_pos) - if (params.includeEntitiesBlocks) { - const blocksBuffer = WorldCompute.computeBlocksBuffer(block_pos) - const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) - if (lastBlockIndex >= 0) { - block.pos.y += lastBlockIndex - block.type = blocksBuffer[lastBlockIndex] as BlockType - } - } - return block - }) - return blocksBatch + computeBlocksBatch( + blockPosBatch: Vector3[], + params = { includeEntitiesBlocks: true }, + ) { + return WorldCompute.computeBlocksBatch(blockPosBatch, params) } *iterPatchCompute(patchKeysBatch: PatchKey[]) { @@ -62,6 +61,7 @@ export class WorldComputeApi implements ComputeApiInterface { * Proxying requests to worker instead of internal world compute */ export class WorldComputeProxy implements ComputeApiInterface { + // eslint-disable-next-line no-undef worker: Worker count = 0 resolvers: Record = {} @@ -114,10 +114,10 @@ export class WorldComputeProxy implements ComputeApiInterface { // const emptyPatch = new BlocksPatch(patchKey) const patchStub = await this.workerCall( ComputeApiCall.PatchCompute, - [patchKey] //[emptyPatch.bbox] + [patchKey], // [emptyPatch.bbox] ) const patch = BlocksPatch.fromStub(patchStub) yield patch } } -} \ No newline at end of file +} diff --git a/src/compute/world-compute.ts b/src/compute/world-compute.ts index fd48ba3..51301b2 100644 --- a/src/compute/world-compute.ts +++ b/src/compute/world-compute.ts @@ -8,18 +8,41 @@ import { EntityData, RepeatableEntitiesMap, } from '../procgen/EntitiesMap' - -import { BlockData, BlocksContainer, BlocksPatch, EntityChunk } from '../data/DataContainers' +import { + BlockData, + BlocksContainer, + BlocksPatch, + EntityChunk, +} from '../data/DataContainers' import { PatchKey } from '../common/types' - - export const computePatch = (patchKey: PatchKey) => { +export const computePatch = (patchKey: PatchKey) => { const patch = new BlocksPatch(patchKey) genGroundBlocks(patch) genEntitiesBlocks(patch) return patch } +export const computeBlocksBatch = ( + blockPosBatch: Vector3[], + params = { includeEntitiesBlocks: true }, +) => { + const blocksBatch = blockPosBatch.map(({ x, z }) => { + const block_pos = new Vector3(x, 0, z) + const block = computeGroundBlock(block_pos) + if (params.includeEntitiesBlocks) { + const blocksBuffer = computeBlocksBuffer(block_pos) + const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) + if (lastBlockIndex >= 0) { + block.pos.y += lastBlockIndex + block.type = blocksBuffer[lastBlockIndex] as BlockType + } + } + return block + }) + return blocksBatch +} + export const computeGroundBlock = (blockPos: Vector3) => { const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) const mainBiome = Biome.instance.getMainBiome(biomeContribs) @@ -60,11 +83,7 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { entity.bbox.min.y = entityLevel entity.bbox.max.y = entityLevel + 10 entity.type = entityType - blocksBuffer = EntitiesMap.fillBlockBuffer( - blockPos, - entity, - blocksBuffer, - ) + blocksBuffer = EntitiesMap.fillBlockBuffer(blockPos, entity, blocksBuffer) } } return blocksBuffer @@ -93,7 +112,9 @@ const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { } const genEntitiesBlocks = (blocksContainer: BlocksContainer) => { - const entitiesIter = RepeatableEntitiesMap.instance.iterate(blocksContainer.bbox) + const entitiesIter = RepeatableEntitiesMap.instance.iterate( + blocksContainer.bbox, + ) for (const entity of entitiesIter) { // use global coords in case entity center is from adjacent patch const entityPos = entity.bbox.getCenter(new Vector3()) @@ -121,7 +142,7 @@ const genEntitiesBlocks = (blocksContainer: BlocksContainer) => { } /** - * Fill container with ground blocks + * Fill container with ground blocks */ const genGroundBlocks = (blocksContainer: BlocksContainer) => { const { min, max } = blocksContainer.bbox @@ -129,7 +150,11 @@ const genGroundBlocks = (blocksContainer: BlocksContainer) => { // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const blocksPatchIter = blocksContainer.iterOverBlocks(undefined, false, false) + const blocksPatchIter = blocksContainer.iterOverBlocks( + undefined, + false, + false, + ) min.y = 512 max.y = 0 let blockIndex = 0 @@ -151,4 +176,4 @@ const genGroundBlocks = (blocksContainer: BlocksContainer) => { // blocksContainer.state = PatchState.Filled return blocksContainer -} \ No newline at end of file +} diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index c6f27df..d72a1ae 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,84 +1,74 @@ -import { Vector3 } from "three"; -import { vect3ToVect2 } from "../common/utils"; -import { BlockType } from "../procgen/Biome"; -import { BlocksContainer, PatchContainer } from "./DataContainers"; +import { Vector3 } from 'three' -export class BoardContainer extends PatchContainer { - boardCenter - boardRadius +import { vect3ToVect2 } from '../common/utils' + +import { PatchContainer } from './DataContainers' - constructor(center: Vector3, radius: number) { - super(); - this.boardRadius = radius - this.boardCenter = vect3ToVect2(center).floor() - const board_dims = new Vector3( - radius, - 0, - radius, - ).multiplyScalar(2) - this.bbox.setFromCenterAndSize( - center.clone().floor(), - board_dims, - ) - this.init(this.bbox) - } +export class BoardContainer extends PatchContainer { + boardCenter + boardRadius - getMinMax() { - const { boardCenter, boardRadius } = this - let ymin = this.bbox.max.y - let ymax = this.bbox.min.y - this.availablePatches.forEach(patch => { - const blocks = patch.iterOverBlocks(this.bbox) - for (const block of blocks) { - // discard blocs not included in board shape - const dist = vect3ToVect2(block.pos) - .distanceTo(boardCenter) - if (dist <= boardRadius) { - const block_level = block.pos.y - ymin = Math.min(block_level, ymin) - ymax = Math.max(block_level, ymax) - } - } - }) - return { ymin, ymax } - } + constructor(center: Vector3, radius: number) { + super() + this.boardRadius = radius + this.boardCenter = vect3ToVect2(center).floor() + const board_dims = new Vector3(radius, 0, radius).multiplyScalar(2) + this.bbox.setFromCenterAndSize(center.clone().floor(), board_dims) + this.init(this.bbox) + } - shapeBoard() { - const { boardCenter, boardRadius } = this - const { ymin, ymax } = this.getMinMax() - const avg = Math.round(ymin + (ymax - ymin) / 2) - this.availablePatches.forEach(patch => { - const blocks = patch.iterOverBlocks(this.bbox) - for (const block of blocks) { - // discard blocs not included in board shape - const dist = vect3ToVect2(block.pos) - .distanceTo(boardCenter,) - const y_diff = Math.abs(block.pos.y - avg) - if (dist <= boardRadius && y_diff <= 5) { - patch.writeBlockAtIndex(block.index, avg, block.type) - } - } - }) - } + getMinMax() { + const { boardCenter, boardRadius } = this + let ymin = this.bbox.max.y + let ymax = this.bbox.min.y + this.availablePatches.forEach(patch => { + const blocks = patch.iterOverBlocks(this.bbox) + for (const block of blocks) { + // discard blocs not included in board shape + const dist = vect3ToVect2(block.pos).distanceTo(boardCenter) + if (dist <= boardRadius) { + const block_level = block.pos.y + ymin = Math.min(block_level, ymin) + ymax = Math.max(block_level, ymax) + } + } + }) + return { ymin, ymax } + } - mergeBoardBlocks(blocksContainer: BlocksContainer) { - // for each patch override with blocks from blocks container - this.availablePatches.forEach(patch => { - const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) - for (const target_block of blocksIter) { - const source_block = blocksContainer.getBlock(target_block.pos, false) - if (source_block && source_block.pos.y > 0 && target_block.index) { - let block_type = source_block.type ? BlockType.MUD : BlockType.NONE - block_type = source_block.type === BlockType.TREE_TRUNK ? BlockType.TREE_TRUNK : block_type - const block_level = blocksContainer.bbox.min.y//source_block?.pos.y - patch.writeBlockAtIndex(target_block.index, block_level, block_type) - // console.log(source_block?.pos.y) - } - } - }) - } + shapeBoard() { + const { boardCenter, boardRadius } = this + const { ymin, ymax } = this.getMinMax() + const avg = Math.round(ymin + (ymax - ymin) / 2) + this.availablePatches.forEach(patch => { + const blocks = patch.iterOverBlocks(this.bbox) + for (const block of blocks) { + // discard blocs not included in board shape + const dist = vect3ToVect2(block.pos).distanceTo(boardCenter) + const y_diff = Math.abs(block.pos.y - avg) + if (dist <= boardRadius && y_diff <= 5 && block.index !== undefined) { + patch.writeBlockAtIndex(block.index, avg, block.type) + } + } + }) + } - smoothEdges() { + // mergeBoardBlocks(blocksContainer: BlocksContainer) { + // // for each patch override with blocks from blocks container + // this.availablePatches.forEach(patch => { + // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + // for (const target_block of blocksIter) { + // const source_block = blocksContainer.getBlock(target_block.pos, false) + // if (source_block && source_block.pos.y > 0 && target_block.index) { + // let block_type = source_block.type ? BlockType.MUD : BlockType.NONE + // block_type = source_block.type === BlockType.TREE_TRUNK ? BlockType.TREE_TRUNK : block_type + // const block_level = blocksContainer.bbox.min.y//source_block?.pos.y + // patch.writeBlockAtIndex(target_block.index, block_level, block_type) + // // console.log(source_block?.pos.y) + // } + // } + // }) + // } - } -} \ No newline at end of file + smoothEdges() {} +} diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index 4a21cdd..f737276 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -1,17 +1,15 @@ import { Box3, Vector3 } from 'three' + import { PatchKey } from '../common/types' import { WorldComputeApi } from '../index' -import { - BlocksPatch, - EntityChunk, - PatchContainer, -} from './DataContainers' +import { BlocksPatch, EntityChunk, PatchContainer } from './DataContainers' /** * Blocks cache */ export class CacheContainer extends PatchContainer { + // eslint-disable-next-line no-use-before-define static singleton: CacheContainer pendingRefresh = false static cachePowRadius = 2 @@ -42,38 +40,32 @@ export class CacheContainer extends PatchContainer { } /** - * - * @param center - * @param dryRun + * + * @param center + * @param dryRun * @returns true if cache was update, false otherwise */ - async refresh( - bbox: Box3, - dryRun = false - ) { - const changes: any = { - count: 0, - batch: [] - } + async refresh(bbox: Box3, dryRun = false) { + let changesDiff if (!this.pendingRefresh) { const emptyContainer = new PatchContainer() emptyContainer.init(bbox) - const diff = emptyContainer.diffWithPatchContainer(CacheContainer.instance) - changes.count = Object.keys(diff).length + changesDiff = emptyContainer.compareWith(CacheContainer.instance) + const hasChanged = Object.keys(changesDiff).length > 0 // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) - if (changes.count) { + if (hasChanged) { // backup patches that will remain in cache const backup = this.availablePatches.filter(patch => patch) // reinit cache super.init(bbox) // restore remaining patches backup this.populateFromExisting(backup) - // return patch keys needing to be retrieved - changes.batch = dryRun ? this.missingPatchKeys : await this.populate(this.missingPatchKeys) + !dryRun && (await this.populate(this.missingPatchKeys)) } } - return changes + // return patch keys changes + return changesDiff } getPatches(inputBbox: Box3) { diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 2229b37..c21baeb 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -1,10 +1,18 @@ import { Box3, Vector2, Vector3 } from 'three' + import { PatchKey } from '../common/types' -import { genChunkIds, parseThreeStub, vect2ToVect3 } from '../common/utils' +import { + computePatchKey, + convertPosToPatchId, + genChunkIds, + getBboxFromPatchKey, + parsePatchKey, + parseThreeStub, + vect2ToVect3, +} from '../common/utils' import { ChunkTools } from '../index' import { BlockType } from '../procgen/Biome' - export type BlockData = { pos: Vector3 type: BlockType @@ -45,8 +53,8 @@ export class BlocksContainer { margin = 0 groundBlocks: { - type: Uint16Array, - level: Uint16Array, + type: Uint16Array + level: Uint16Array } entitiesChunks: EntityChunk[] = [] @@ -64,8 +72,12 @@ export class BlocksContainer { duplicate() { const duplicate = new BlocksContainer(this.bbox) - this.groundBlocks.level.forEach((v, i) => duplicate.groundBlocks.level[i] = v) - this.groundBlocks.type.forEach((v, i) => duplicate.groundBlocks.type[i] = v) + this.groundBlocks.level.forEach( + (v, i) => (duplicate.groundBlocks.level[i] = v), + ) + this.groundBlocks.type.forEach( + (v, i) => (duplicate.groundBlocks.type[i] = v), + ) return duplicate } @@ -87,7 +99,10 @@ export class BlocksContainer { } get localExtendedBox() { - const bbox = new Box3(new Vector3(0), this.dimensions.clone()).expandByScalar(this.margin) + const bbox = new Box3( + new Vector3(0), + this.dimensions.clone(), + ).expandByScalar(this.margin) return bbox } @@ -99,15 +114,25 @@ export class BlocksContainer { Math.max(Math.floor(bbox.min.z), useLocalPos ? 0 : this.bbox.min.z), ) const bmax = new Vector3( - Math.min(Math.floor(bbox.max.x), useLocalPos ? patchSize : this.bbox.max.x), + Math.min( + Math.floor(bbox.max.x), + useLocalPos ? patchSize : this.bbox.max.x, + ), 0, - Math.min(Math.floor(bbox.max.z), useLocalPos ? patchSize : this.bbox.max.z), + Math.min( + Math.floor(bbox.max.z), + useLocalPos ? patchSize : this.bbox.max.z, + ), ) return new Box3(bmin, bmax) } getBlockIndex(localPos: Vector3) { - return (localPos.x + this.margin) * this.extendedDims.x + localPos.z + this.margin + return ( + (localPos.x + this.margin) * this.extendedDims.x + + localPos.z + + this.margin + ) } getLocalPos(pos: Vector3) { @@ -145,11 +170,19 @@ export class BlocksContainer { } *iterOverBlocks(customBox?: Box3, useLocalPos = false, skipMargin = true) { - const bbox = customBox ? this.adaptCustomBox(customBox, useLocalPos) : - useLocalPos ? this.localExtendedBox : this.extendedBox - - const isMarginBlock = ({ x, z }: { x: number, z: number }) => !customBox && this.margin > 0 - && (x === bbox.min.x || x === bbox.max.x - 1 || z === bbox.min.z || z === bbox.max.z - 1) + const bbox = customBox + ? this.adaptCustomBox(customBox, useLocalPos) + : useLocalPos + ? this.localExtendedBox + : this.extendedBox + + const isMarginBlock = ({ x, z }: { x: number; z: number }) => + !customBox && + this.margin > 0 && + (x === bbox.min.x || + x === bbox.max.x - 1 || + z === bbox.min.z || + z === bbox.max.z - 1) let index = 0 for (let { x } = bbox.min; x < bbox.max.x; x++) { @@ -180,7 +213,8 @@ export class BlocksContainer { blockPos.x >= this.bbox.min.x && blockPos.z >= this.bbox.min.z && blockPos.x < this.bbox.max.x && - blockPos.z < this.bbox.max.z) + blockPos.z < this.bbox.max.z + ) } toChunk() { @@ -197,7 +231,6 @@ export class BlocksContainer { // ) return blocksContainer } - } /** @@ -213,23 +246,27 @@ export class BlocksPatch extends BlocksContainer { key: string constructor(patchKey: string) { - super(BlocksPatch.getBboxFromPatchKey(patchKey))//.expandByScalar(1)) + super(getBboxFromPatchKey(patchKey, BlocksPatch.patchSize)) // .expandByScalar(1)) this.key = patchKey - const patchCoords = BlocksPatch.parsePatchKey(patchKey) - this.coords = new Vector2(patchCoords.x, patchCoords.z) + this.coords = parsePatchKey(patchKey) } override duplicate() { const duplicate = new BlocksPatch(this.key) - this.groundBlocks.level.forEach((v, i) => duplicate.groundBlocks.level[i] = v) - this.groundBlocks.type.forEach((v, i) => duplicate.groundBlocks.type[i] = v) + this.groundBlocks.level.forEach( + (v, i) => (duplicate.groundBlocks.level[i] = v), + ) + this.groundBlocks.type.forEach( + (v, i) => (duplicate.groundBlocks.type[i] = v), + ) return duplicate } static override fromStub(patchStub: any) { const { groundBlocks, entitiesChunks } = patchStub const bbox = parseThreeStub(patchStub.bbox) - const patchKey = patchStub.key || this.computePatchKey(bbox) + const patchKey = + patchStub.key || computePatchKey(bbox, BlocksPatch.patchSize) const patch = new BlocksPatch(patchKey) patch.groundBlocks = groundBlocks patch.entitiesChunks = entitiesChunks @@ -241,55 +278,8 @@ export class BlocksPatch extends BlocksContainer { return patch } - static asPatchCoords = (position: Vector3) => { - const { patchSize } = this - const orig_x = Math.floor(position.x / patchSize); - const orig_z = Math.floor(position.z / patchSize); - const patchCoords = new Vector2(orig_x, orig_z); - return patchCoords - } - - static parsePatchKey = (patchKey: string) => { - const patchOrigin = new Vector3( - parseInt(patchKey.split('_')[1] as string), - 0, - parseInt(patchKey.split('_')[2] as string), - ) - return patchOrigin - } - - static getBboxFromPatchKey = (patchKey: string) => { - const { patchSize } = BlocksPatch - const patchCoords = BlocksPatch.parsePatchKey(patchKey) - const bmin = patchCoords.clone().multiplyScalar(patchSize) - const bmax = patchCoords.clone().addScalar(1).multiplyScalar(patchSize) - bmax.y = 512 - const bbox = new Box3(bmin, bmax) - return bbox - } - - static computePatchKey(input: Box3 | Vector3 | Vector2) { - const inputCopy: Vector3 | Box3 = - input instanceof Vector2 - ? new Vector3(input.x, 0, input.y) - : input.clone() - const point = - inputCopy instanceof Box3 - ? (inputCopy as Box3).getCenter(new Vector3()) - : (inputCopy as Vector3).clone() - - const patchOrigin = this.asPatchCoords(point) - const { x, y } = patchOrigin - const patchKey = `patch_${x}_${y}` - return patchKey - } - toChunks(yMin: number, yMax: number) { - const chunkIds = genChunkIds( - this.coords, - yMin, - yMax, - ) + const chunkIds = genChunkIds(this.coords, yMin, yMax) const chunks = chunkIds.map(chunkId => ChunkTools.makeChunkFromId(this, chunkId), ) @@ -302,9 +292,13 @@ export class PatchContainer { patchLookup: Record = {} get patchIdsRange() { - const rangeMin = BlocksPatch.asPatchCoords(this.bbox.min) - const rangeMax = BlocksPatch.asPatchCoords(this.bbox.max).addScalar(1) - const patchIdsRange = new Box3(vect2ToVect3(rangeMin), vect2ToVect3(rangeMax)) + const { patchSize } = BlocksPatch + const rangeMin = convertPosToPatchId(this.bbox.min, patchSize) + const rangeMax = convertPosToPatchId(this.bbox.max, patchSize).addScalar(1) + const patchIdsRange = new Box3( + vect2ToVect3(rangeMin), + vect2ToVect3(rangeMax), + ) return patchIdsRange } @@ -316,9 +310,9 @@ export class PatchContainer { // const center = this.bbox.getCenter(new Vector3()) // const origin = BlocksPatch.asPatchCoords(center) const { min, max } = this.patchIdsRange - for (let x = min.x; x < max.x; x++) { - for (let z = min.z; z < max.z; z++) { - const patchKey = 'patch_' + x + '_' + z; + for (let { x } = min; x < max.x; x++) { + for (let { z } = min; z < max.z; z++) { + const patchKey = 'patch_' + x + '_' + z this.patchLookup[patchKey] = null } } @@ -329,7 +323,9 @@ export class PatchContainer { } get missingPatchKeys() { - return Object.keys(this.patchLookup).filter(key => !this.patchLookup[key]) as PatchKey[] + return Object.keys(this.patchLookup).filter( + key => !this.patchLookup[key], + ) as PatchKey[] } get count() { @@ -347,7 +343,8 @@ export class PatchContainer { populateFromExisting(patches: BlocksPatch[], cloneObjects = false) { const { min, max } = this.bbox - patches.filter(patch => this.patchLookup[patch.key] !== undefined) + patches + .filter(patch => this.patchLookup[patch.key] !== undefined) .forEach(patch => { this.patchLookup[patch.key] = cloneObjects ? patch.duplicate() : patch min.y = Math.min(patch.bbox.min.y, min.y) @@ -363,8 +360,11 @@ export class PatchContainer { const source_block = blocksContainer.getBlock(target_block.pos, false) if (source_block && source_block.pos.y > 0 && target_block.index) { let block_type = source_block.type ? BlockType.SAND : BlockType.NONE - block_type = source_block.type === BlockType.TREE_TRUNK ? BlockType.TREE_TRUNK : block_type - const block_level = blocksContainer.bbox.min.y//source_block?.pos.y + block_type = + source_block.type === BlockType.TREE_TRUNK + ? BlockType.TREE_TRUNK + : block_type + const block_level = blocksContainer.bbox.min.y // source_block?.pos.y patch.writeBlockAtIndex(target_block.index, block_level, block_type) // console.log(source_block?.pos.y) } @@ -372,21 +372,23 @@ export class PatchContainer { }) } - diffWithPatchContainer(otherContainer: PatchContainer) { + compareWith(otherContainer: PatchContainer) { const patchKeysDiff: Record = {} // added keys e.g. keys in current container but not found in other Object.keys(this.patchLookup) .filter(patchKey => otherContainer.patchLookup[patchKey] === undefined) - .forEach(patchKey => patchKeysDiff[patchKey] = true) + .forEach(patchKey => (patchKeysDiff[patchKey] = true)) // missing keys e.g. found in other container but not in current Object.keys(otherContainer.patchLookup) .filter(patchKey => this.patchLookup[patchKey] === undefined) - .forEach(patchKey => patchKeysDiff[patchKey] = false) + .forEach(patchKey => (patchKeysDiff[patchKey] = false)) return patchKeysDiff } toChunks(yMin: number, yMax: number) { - const chunksExport = this.availablePatches.map(patch => patch.toChunks(yMin, yMax)).flat() + const chunksExport = this.availablePatches + .map(patch => patch.toChunks(yMin, yMax)) + .flat() return chunksExport } @@ -397,7 +399,9 @@ export class PatchContainer { // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, // ) - const res = this.availablePatches.find(patch => patch.containsBlock(blockPos)) + const res = this.availablePatches.find(patch => + patch.containsBlock(blockPos), + ) return res } } diff --git a/src/index.ts b/src/index.ts index 3273f6d..12d4235 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,11 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { BlocksContainer, BlocksPatch, PatchContainer } from './data/DataContainers' +export { + BlocksContainer, + BlocksPatch, + PatchContainer, +} from './data/DataContainers' export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' diff --git a/src/utils/chunk_tools.ts b/src/utils/chunk_tools.ts index 477527a..eca02d4 100644 --- a/src/utils/chunk_tools.ts +++ b/src/utils/chunk_tools.ts @@ -1,18 +1,16 @@ -import { Box3, MathUtils, Vector3 } from "three" -import { ChunkId, WorldChunk } from "../common/types" -import { getChunkBboxFromId, serializeChunkId } from "../common/utils" -import { - BlocksContainer, - BlocksPatch, - BlockType, -} from '../index' +import { Box3, MathUtils, Vector3 } from 'three' + +import { ChunkId, WorldChunk } from '../common/types' +import { getBboxFromChunkId, serializeChunkId } from '../common/utils' +import { BlocksContainer, BlocksPatch, BlockType } from '../index' const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return DBG_BORDERS_HIGHLIGHT_COLOR && (localPos.x === 1 || localPos.z === 1) ? - DBG_BORDERS_HIGHLIGHT_COLOR : blockType + return DBG_BORDERS_HIGHLIGHT_COLOR && (localPos.x === 1 || localPos.z === 1) + ? DBG_BORDERS_HIGHLIGHT_COLOR + : blockType } const writeChunkBlocks = ( @@ -59,7 +57,11 @@ const writeChunkBlocks = ( return written_blocks_count } -const fillGroundData = (blocksContainer: BlocksContainer, chunkData: Uint16Array, chunkBox: Box3) => { +const fillGroundData = ( + blocksContainer: BlocksContainer, + chunkData: Uint16Array, + chunkBox: Box3, +) => { let written_blocks_count = 0 const blocks_iter = blocksContainer.iterOverBlocks(undefined, true, false) for (const block of blocks_iter) { @@ -67,7 +69,8 @@ const fillGroundData = (blocksContainer: BlocksContainer, chunkData: Uint16Array blockLocalPos.x += 1 // blockLocalPos.y = patch.bbox.max.y blockLocalPos.z += 1 - const blockType = highlightPatchBorders(blockLocalPos, block.type) || block.type + const blockType = + highlightPatchBorders(blockLocalPos, block.type) || block.type written_blocks_count += writeChunkBlocks( chunkData, chunkBox, @@ -78,7 +81,11 @@ const fillGroundData = (blocksContainer: BlocksContainer, chunkData: Uint16Array return written_blocks_count } -const fillEntitiesData = (blocksContainer: BlocksContainer, chunkData: Uint16Array, chunkBox: Box3) => { +const fillEntitiesData = ( + blocksContainer: BlocksContainer, + chunkData: Uint16Array, + chunkBox: Box3, +) => { let written_blocks_count = 0 // iter over container entities for (const entity_chunk of blocksContainer.entitiesChunks) { @@ -93,8 +100,7 @@ const fillEntitiesData = (blocksContainer: BlocksContainer, chunkData: Uint16Arr for (const block of blocks_iter) { const bufferStr = entity_chunk.data[chunk_index] const buffer = - bufferStr && - bufferStr.split(',').map(char => parseInt(char)) + bufferStr && bufferStr.split(',').map(char => parseInt(char)) if (buffer && block.localPos) { block.buffer = buffer block.localPos.x += 1 @@ -114,17 +120,23 @@ const fillEntitiesData = (blocksContainer: BlocksContainer, chunkData: Uint16Arr return written_blocks_count } -export function makeChunkFromId(blocksContainer: BlocksContainer, chunkId: ChunkId) { - const chunkBox = getChunkBboxFromId(chunkId, BlocksPatch.patchSize) +export function makeChunkFromId( + blocksContainer: BlocksContainer, + chunkId: ChunkId, +) { + const chunkBox = getBboxFromChunkId(chunkId, BlocksPatch.patchSize) const chunk = makeChunkFromBox(blocksContainer, chunkBox) const regularChunk: WorldChunk = { key: serializeChunkId(chunkId), - data: chunk.data + data: chunk.data, } return regularChunk } -export function makeChunkFromBox(blocksContainer: BlocksContainer, _chunkBox?: Box3) { +export function makeChunkFromBox( + blocksContainer: BlocksContainer, + _chunkBox?: Box3, +) { const chunkBox = _chunkBox || blocksContainer.bbox const chunkDims = chunkBox.getSize(new Vector3()) const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) @@ -148,17 +160,9 @@ export function makeChunkFromBox(blocksContainer: BlocksContainer, _chunkBox?: B // multi-pass chunk filling if (blocksContainer) { // ground pass - totalWrittenBlocks += fillGroundData( - blocksContainer, - chunkData, - chunkBox, - ) + totalWrittenBlocks += fillGroundData(blocksContainer, chunkData, chunkBox) // overground entities pass - totalWrittenBlocks += fillEntitiesData( - blocksContainer, - chunkData, - chunkBox, - ) + totalWrittenBlocks += fillEntitiesData(blocksContainer, chunkData, chunkBox) } // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) // const dimensions = new Vector3(size, size, size) diff --git a/src/utils/plateau_legacy.ts b/src/utils/plateau_legacy.ts index dc9eb70..282aa48 100644 --- a/src/utils/plateau_legacy.ts +++ b/src/utils/plateau_legacy.ts @@ -2,307 +2,306 @@ // import { voxelmapDataPacking, type IVoxelMap } from '../i-voxelmap'; import { Box3, Vector3, Vector3Like } from 'three' + import { ChunkTools } from '../index' import { BlocksContainer } from '../data/DataContainers' enum EPlateauSquareType { - FLAT = 0, - HOLE = 1, - OBSTACLE = 2, + FLAT = 0, + HOLE = 1, + OBSTACLE = 2, } type PlateauSquare = { - readonly type: EPlateauSquareType - readonly materialId: number + readonly type: EPlateauSquareType + readonly materialId: number } type ColumnId = { readonly x: number; readonly z: number } type Plateau = { - readonly id: number - readonly size: { readonly x: number; readonly z: number } - readonly squares: ReadonlyArray - readonly origin: Vector3Like + readonly id: number + readonly size: { readonly x: number; readonly z: number } + readonly squares: ReadonlyArray + readonly origin: Vector3Like } type PlateauSquareExtended = PlateauSquare & { - readonly floorY: number - readonly generation: number + readonly floorY: number + readonly generation: number } let plateauxCount = 0 -async function computePlateau( - originWorld: Vector3Like, -): Promise { - originWorld = { - x: Math.floor(originWorld.x), - y: Math.floor(originWorld.y), - z: Math.floor(originWorld.z), - } +async function computePlateau(originWorld: Vector3Like): Promise { + originWorld = { + x: Math.floor(originWorld.x), + y: Math.floor(originWorld.y), + z: Math.floor(originWorld.z), + } - let currentGeneration = 0 - const maxDeltaY = 4 - const plateauHalfSize = 31 - const plateauSize = { x: 2 * plateauHalfSize + 1, z: 2 * plateauHalfSize + 1 } - const plateauSquares: PlateauSquareExtended[] = [] - for (let iZ = 0; iZ < plateauSize.z; iZ++) { - for (let iX = 0; iX < plateauSize.x; iX++) { - plateauSquares.push({ - type: EPlateauSquareType.HOLE, - materialId: 0, - floorY: NaN, - generation: currentGeneration, - }) - } - } - const tryGetIndex = (relativePos: ColumnId) => { - const plateauCoords = { - x: relativePos.x + plateauHalfSize, - z: relativePos.z + plateauHalfSize, - } - if ( - plateauCoords.x < 0 || - plateauCoords.z < 0 || - plateauCoords.x >= plateauSize.x || - plateauCoords.z >= plateauSize.z - ) { - return null - } - return plateauCoords.x + plateauCoords.z * plateauSize.x + let currentGeneration = 0 + const maxDeltaY = 4 + const plateauHalfSize = 31 + const plateauSize = { x: 2 * plateauHalfSize + 1, z: 2 * plateauHalfSize + 1 } + const plateauSquares: PlateauSquareExtended[] = [] + for (let iZ = 0; iZ < plateauSize.z; iZ++) { + for (let iX = 0; iX < plateauSize.x; iX++) { + plateauSquares.push({ + type: EPlateauSquareType.HOLE, + materialId: 0, + floorY: NaN, + generation: currentGeneration, + }) } - const getIndex = (relativePos: ColumnId) => { - const index = tryGetIndex(relativePos) - if (index === null) { - throw new Error() - } - return index + } + const tryGetIndex = (relativePos: ColumnId) => { + const plateauCoords = { + x: relativePos.x + plateauHalfSize, + z: relativePos.z + plateauHalfSize, } - const setPlateauSquare = ( - relativePos: ColumnId, - square: PlateauSquareExtended, - ) => { - const index = getIndex(relativePos) - plateauSquares[index] = { ...square } + if ( + plateauCoords.x < 0 || + plateauCoords.z < 0 || + plateauCoords.x >= plateauSize.x || + plateauCoords.z >= plateauSize.z + ) { + return null } - const getPlateauSquare = (relativePos: ColumnId) => { - const index = getIndex(relativePos) - return plateauSquares[index]! + return plateauCoords.x + plateauCoords.z * plateauSize.x + } + const getIndex = (relativePos: ColumnId) => { + const index = tryGetIndex(relativePos) + if (index === null) { + throw new Error() } - const tryGetPlateauSquare = (relativePos: ColumnId) => { - const index = tryGetIndex(relativePos) - if (index === null) { - return null - } - return plateauSquares[index] + return index + } + const setPlateauSquare = ( + relativePos: ColumnId, + square: PlateauSquareExtended, + ) => { + const index = getIndex(relativePos) + plateauSquares[index] = { ...square } + } + const getPlateauSquare = (relativePos: ColumnId) => { + const index = getIndex(relativePos) + return plateauSquares[index]! + } + const tryGetPlateauSquare = (relativePos: ColumnId) => { + const index = tryGetIndex(relativePos) + if (index === null) { + return null } + return plateauSquares[index] + } - const dataMargin = plateauHalfSize + 5 - const dataFromWorld = new Vector3().copy(originWorld).subScalar(dataMargin) - const dataToWorld = new Vector3().copy(originWorld).addScalar(dataMargin) - const dataBbox = new Box3(dataFromWorld, dataToWorld) - const containerStub: any = null //await WorldCompute.instance.iterPatchCompute() - const blocksContainer = BlocksContainer.fromStub(containerStub) - const chunk = ChunkTools.makeChunkFromBox(blocksContainer, dataBbox) - const data = chunk//await map.getLocalMapData(dataFromWorld, dataToWorld) - const dataSize = dataToWorld.clone().sub(dataFromWorld) + const dataMargin = plateauHalfSize + 5 + const dataFromWorld = new Vector3().copy(originWorld).subScalar(dataMargin) + const dataToWorld = new Vector3().copy(originWorld).addScalar(dataMargin) + const dataBbox = new Box3(dataFromWorld, dataToWorld) + const containerStub: any = null // await WorldCompute.instance.iterPatchCompute() + const blocksContainer = BlocksContainer.fromStub(containerStub) + const chunk = ChunkTools.makeChunkFromBox(blocksContainer, dataBbox) + const data = chunk // await map.getLocalMapData(dataFromWorld, dataToWorld) + const dataSize = dataToWorld.clone().sub(dataFromWorld) - const sampleData = (worldPos: Vector3Like) => { - const dataPos = new Vector3().copy(worldPos).sub(dataFromWorld) - if ( - dataPos.x < 0 || - dataPos.y < 0 || - dataPos.z < 0 || - dataPos.x >= dataSize.x || - dataPos.y >= dataSize.y || - dataPos.z >= dataSize.z - ) { - throw new Error() - } - const index = - dataPos.x + dataPos.y * dataSize.x + dataPos.z * dataSize.x * dataSize.y - return data.data[index]! + const sampleData = (worldPos: Vector3Like) => { + const dataPos = new Vector3().copy(worldPos).sub(dataFromWorld) + if ( + dataPos.x < 0 || + dataPos.y < 0 || + dataPos.z < 0 || + dataPos.x >= dataSize.x || + dataPos.y >= dataSize.y || + dataPos.z >= dataSize.z + ) { + throw new Error() } + const index = + dataPos.x + dataPos.y * dataSize.x + dataPos.z * dataSize.x * dataSize.y + return data.data?.[index]! + } - { - const originWorldCoords = { - x: originWorld.x, - y: originWorld.y, - z: originWorld.z, - } - let originSample = sampleData(originWorldCoords) - let deltaY = 0 - while (!originSample && deltaY < maxDeltaY) { - originWorldCoords.y-- - deltaY++ - originSample = sampleData(originWorldCoords) - } - if (!originSample) { - throw new Error() - } - setPlateauSquare( - { x: 0, z: 0 }, - { - type: EPlateauSquareType.FLAT, - materialId: originSample, - generation: currentGeneration, - floorY: originWorldCoords.y - 1, - }, - ) + { + const originWorldCoords = { + x: originWorld.x, + y: originWorld.y, + z: originWorld.z, } - const originY = getPlateauSquare({ x: 0, z: 0 })!.floorY - - const computePlateauSquare = ( - relativePos: ColumnId, - ): PlateauSquareExtended | null => { - const square = getPlateauSquare(relativePos) - if (square.type !== EPlateauSquareType.HOLE) { - // this square has been computed already - return null - } + let originSample = sampleData(originWorldCoords) + let deltaY = 0 + while (!originSample && deltaY < maxDeltaY) { + originWorldCoords.y-- + deltaY++ + originSample = sampleData(originWorldCoords) + } + if (!originSample) { + throw new Error() + } + setPlateauSquare( + { x: 0, z: 0 }, + { + type: EPlateauSquareType.FLAT, + materialId: originSample, + generation: currentGeneration, + floorY: originWorldCoords.y - 1, + }, + ) + } + const originY = getPlateauSquare({ x: 0, z: 0 })!.floorY - // if this square has not been computed yet - const xm = tryGetPlateauSquare({ x: relativePos.x - 1, z: relativePos.z }) - const xp = tryGetPlateauSquare({ x: relativePos.x + 1, z: relativePos.z }) - const zm = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z - 1 }) - const zp = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z + 1 }) + const computePlateauSquare = ( + relativePos: ColumnId, + ): PlateauSquareExtended | null => { + const square = getPlateauSquare(relativePos) + if (square.type !== EPlateauSquareType.HOLE) { + // this square has been computed already + return null + } - const worldPos = { x: 0, y: 0, z: 0 } - worldPos.x = relativePos.x + originWorld.x - worldPos.z = relativePos.z + originWorld.z + // if this square has not been computed yet + const xm = tryGetPlateauSquare({ x: relativePos.x - 1, z: relativePos.z }) + const xp = tryGetPlateauSquare({ x: relativePos.x + 1, z: relativePos.z }) + const zm = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z - 1 }) + const zp = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z + 1 }) - for (const neighbour of [xm, xp, zm, zp]) { - if ( - neighbour?.type === EPlateauSquareType.FLAT && - neighbour.generation === currentGeneration - 1 - ) { - worldPos.y = neighbour.floorY - const generation = currentGeneration - const sampleY = sampleData(worldPos) + const worldPos = { x: 0, y: 0, z: 0 } + worldPos.x = relativePos.x + originWorld.x + worldPos.z = relativePos.z + originWorld.z - if (sampleY) { - let firstSample: number | null = null - let lastSample = sampleY - for (let deltaY = 1; deltaY < maxDeltaY; deltaY++) { - const sample = sampleData({ - x: worldPos.x, - y: worldPos.y + deltaY, - z: worldPos.z, - }) - if (!sample) { - return { - type: EPlateauSquareType.FLAT, - materialId: lastSample, - floorY: worldPos.y + deltaY - 1, - generation, - } - } else { - firstSample = firstSample ?? sample - lastSample = sample - } - } + for (const neighbour of [xm, xp, zm, zp]) { + if ( + neighbour?.type === EPlateauSquareType.FLAT && + neighbour.generation === currentGeneration - 1 + ) { + worldPos.y = neighbour.floorY + const generation = currentGeneration + const sampleY = sampleData(worldPos) - if (!firstSample) { - throw new Error() - } + if (sampleY) { + let firstSample: number | null = null + let lastSample = sampleY + for (let deltaY = 1; deltaY < maxDeltaY; deltaY++) { + const sample = sampleData({ + x: worldPos.x, + y: worldPos.y + deltaY, + z: worldPos.z, + }) + if (!sample) { + return { + type: EPlateauSquareType.FLAT, + materialId: lastSample, + floorY: worldPos.y + deltaY - 1, + generation, + } + } else { + firstSample = firstSample ?? sample + lastSample = sample + } + } - return { - type: EPlateauSquareType.OBSTACLE, - materialId: firstSample, - floorY: worldPos.y, - generation, - } - } else { - for (let deltaY = -1; deltaY > -maxDeltaY; deltaY--) { - const sample = sampleData({ - x: worldPos.x, - y: worldPos.y + deltaY, - z: worldPos.z, - }) - if (sample) { - return { - type: EPlateauSquareType.FLAT, - materialId: sample, - floorY: worldPos.y + deltaY, - generation, - } - } - } + if (!firstSample) { + throw new Error() + } - return { - type: EPlateauSquareType.HOLE, - materialId: 0, - floorY: NaN, - generation, - } - } + return { + type: EPlateauSquareType.OBSTACLE, + materialId: firstSample, + floorY: worldPos.y, + generation, + } + } else { + for (let deltaY = -1; deltaY > -maxDeltaY; deltaY--) { + const sample = sampleData({ + x: worldPos.x, + y: worldPos.y + deltaY, + z: worldPos.z, + }) + if (sample) { + return { + type: EPlateauSquareType.FLAT, + materialId: sample, + floorY: worldPos.y + deltaY, + generation, + } } - } + } - return null + return { + type: EPlateauSquareType.HOLE, + materialId: 0, + floorY: NaN, + generation, + } + } + } } - let somethingChanged = false - do { - somethingChanged = false - currentGeneration++ + return null + } - const relativePos = { x: 0, z: 0 } - for ( - relativePos.z = -plateauHalfSize; - relativePos.z <= plateauHalfSize; - relativePos.z++ + let somethingChanged = false + do { + somethingChanged = false + currentGeneration++ + + const relativePos = { x: 0, z: 0 } + for ( + relativePos.z = -plateauHalfSize; + relativePos.z <= plateauHalfSize; + relativePos.z++ + ) { + for ( + relativePos.x = -plateauHalfSize; + relativePos.x <= plateauHalfSize; + relativePos.x++ + ) { + if ( + Math.sqrt( + relativePos.x * relativePos.x + relativePos.z * relativePos.z, + ) >= + plateauHalfSize - 1 ) { - for ( - relativePos.x = -plateauHalfSize; - relativePos.x <= plateauHalfSize; - relativePos.x++ - ) { - if ( - Math.sqrt( - relativePos.x * relativePos.x + relativePos.z * relativePos.z, - ) >= - plateauHalfSize - 1 - ) { - continue - } + continue + } - const square = computePlateauSquare(relativePos) - if ( - square && - !isNaN(square.floorY) && - Math.abs(square.floorY - originY) < maxDeltaY - ) { - somethingChanged = true - setPlateauSquare(relativePos, square) - } - } + const square = computePlateauSquare(relativePos) + if ( + square && + !isNaN(square.floorY) && + Math.abs(square.floorY - originY) < maxDeltaY + ) { + somethingChanged = true + setPlateauSquare(relativePos, square) } - } while (somethingChanged) + } + } + } while (somethingChanged) - const minY = plateauSquares.reduce( - (y: number, square: PlateauSquareExtended) => { - if (!isNaN(square.floorY)) { - return Math.min(y, square.floorY) - } - return y - }, - originY, - ) - const plateauYShift = minY - originY - 1 + const minY = plateauSquares.reduce( + (y: number, square: PlateauSquareExtended) => { + if (!isNaN(square.floorY)) { + return Math.min(y, square.floorY) + } + return y + }, + originY, + ) + const plateauYShift = minY - originY - 1 - const plateauOrigin = new Vector3( - originWorld.x - plateauHalfSize, - originWorld.y + plateauYShift, - originWorld.z - plateauHalfSize, - ) + const plateauOrigin = new Vector3( + originWorld.x - plateauHalfSize, + originWorld.y + plateauYShift, + originWorld.z - plateauHalfSize, + ) - return { - id: plateauxCount++, - size: plateauSize, - squares: plateauSquares, - origin: plateauOrigin, - } + return { + id: plateauxCount++, + size: plateauSize, + squares: plateauSquares, + origin: plateauOrigin, + } } export { computePlateau, EPlateauSquareType, type Plateau, type PlateauSquare } From b66c6be026e5794b11ee9e21a91c8284a6560a8e Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 14 Aug 2024 12:32:08 +0000 Subject: [PATCH 07/45] feat: built-in cache allow toggling on/off --- src/data/CacheContainer.ts | 5 +++-- src/index.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index f737276..6af61e9 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -12,6 +12,7 @@ export class CacheContainer extends PatchContainer { // eslint-disable-next-line no-use-before-define static singleton: CacheContainer pendingRefresh = false + builtInCache = false // specify whether cache is managed internally or separately static cachePowRadius = 2 static cacheSize = BlocksPatch.patchSize * 5 // static worldApi = new WorldApi() @@ -45,7 +46,7 @@ export class CacheContainer extends PatchContainer { * @param dryRun * @returns true if cache was update, false otherwise */ - async refresh(bbox: Box3, dryRun = false) { + async refresh(bbox: Box3) { let changesDiff if (!this.pendingRefresh) { const emptyContainer = new PatchContainer() @@ -61,7 +62,7 @@ export class CacheContainer extends PatchContainer { super.init(bbox) // restore remaining patches backup this.populateFromExisting(backup) - !dryRun && (await this.populate(this.missingPatchKeys)) + this.builtInCache && (await this.populate(this.missingPatchKeys)) } } // return patch keys changes diff --git a/src/index.ts b/src/index.ts index 12d4235..7bd90f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' -export { CacheContainer } from './data/CacheContainer' +export { CacheContainer as WorldCacheContainer } from './data/CacheContainer' export { WorldComputeApi } from './compute/WorldComputeApi' export * as WorldCompute from './compute/world-compute' export * as WorldUtils from './common/utils' From 23ce824ec7de5ed9486dbbd92a35b50b68dee5e9 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 14 Aug 2024 18:08:23 +0000 Subject: [PATCH 08/45] fix: defaulting to return ground (without entities) for blocks batch request --- src/compute/world-compute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compute/world-compute.ts b/src/compute/world-compute.ts index 51301b2..e2b0fde 100644 --- a/src/compute/world-compute.ts +++ b/src/compute/world-compute.ts @@ -25,7 +25,7 @@ export const computePatch = (patchKey: PatchKey) => { export const computeBlocksBatch = ( blockPosBatch: Vector3[], - params = { includeEntitiesBlocks: true }, + params = { includeEntitiesBlocks: false }, ) => { const blocksBatch = blockPosBatch.map(({ x, z }) => { const block_pos = new Vector3(x, 0, z) From 704c0c87bc333be5e78eb9bbf4aaa16210662cc2 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 15 Aug 2024 19:55:20 +0000 Subject: [PATCH 09/45] feat: misc changes --- src/common/utils.ts | 25 ++++++++---- src/compute/WorldComputeApi.ts | 8 +++- src/compute/world-compute.ts | 27 +++++++------ src/config/WorldConfig.ts | 11 ++++++ src/data/BoardContainer.ts | 5 ++- src/data/CacheContainer.ts | 9 +++-- src/data/DataContainers.ts | 38 +++++++++---------- src/index.ts | 5 ++- src/procgen/EntitiesMap.ts | 5 +-- .../chunk_tools.ts => tools/chunk-factory.ts} | 4 +- 10 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 src/config/WorldConfig.ts rename src/{utils/chunk_tools.ts => tools/chunk-factory.ts} (98%) diff --git a/src/common/utils.ts b/src/common/utils.ts index 7e351cc..64c032b 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,4 +1,5 @@ import { Box3, Vector2, Vector3, Vector3Like } from 'three' +import { WorldConfig } from '../config/WorldConfig' import { Adjacent2dPos, @@ -20,6 +21,15 @@ const roundToDec = (val: number, n_pow: number) => { return Math.round(val * num) / num } +const vectRoundToDec = (input: Vector2 | Vector3, n_pow: number) => { + let { x, y } = input + x = roundToDec(x, n_pow) + y = roundToDec(y, n_pow) + const output = input instanceof Vector3 ? new Vector3(x, y, roundToDec(input.z, n_pow)) : + new Vector2(x, y) + return output +} + // const MappingRangeFinder = (item: LinkedList, inputVal: number) => item.next && inputVal > (item.next.data as MappingData).x export const MappingRangeSorter = (item1: MappingRange, item2: MappingRange) => item1.x - item2.x @@ -229,13 +239,13 @@ const parseThreeStub = (stub: any) => { const parsePatchKey = (patchKey: PatchKey) => { const patchId = new Vector2( - parseInt(patchKey.split('_')[1] as string), - parseInt(patchKey.split('_')[2] as string), + parseInt(patchKey.split(':')[0] as string), + parseInt(patchKey.split(':')[1] as string), ) return patchId } -const convertPosToPatchId = (position: Vector3, patchSize: number) => { +const convertPosToPatchId = (position: Vector3, patchSize: number = WorldConfig.patchSize) => { const orig_x = Math.floor(position.x / patchSize) const orig_z = Math.floor(position.z / patchSize) const patchCoords = new Vector2(orig_x, orig_z) @@ -244,7 +254,7 @@ const convertPosToPatchId = (position: Vector3, patchSize: number) => { const computePatchKey = ( patchId: Box3 | Vector3 | Vector2, - patchSize: number, + patchSize: number = WorldConfig.patchSize, ) => { const inputCopy: Vector3 | Box3 = patchId instanceof Vector2 @@ -257,11 +267,11 @@ const computePatchKey = ( const patchOrigin = convertPosToPatchId(point, patchSize) const { x, y } = patchOrigin - const patchKey = `patch_${x}_${y}` + const patchKey = `${x}:${y}` return patchKey } -const getBboxFromPatchKey = (patchKey: string, patchSize: number) => { +const getBboxFromPatchKey = (patchKey: string, patchSize: number = WorldConfig.patchSize) => { const patchCoords = parsePatchKey(patchKey) const bmin = vect2ToVect3(patchCoords.clone().multiplyScalar(patchSize)) const bmax = vect2ToVect3( @@ -294,7 +304,7 @@ function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { return chunk_ids } -const getBboxFromChunkId = (chunkId: ChunkId, patchSize: number) => { +const getBboxFromChunkId = (chunkId: ChunkId, patchSize: number = WorldConfig.patchSize) => { const bmin = chunkId.clone().multiplyScalar(patchSize) const bmax = chunkId.clone().addScalar(1).multiplyScalar(patchSize) const chunkBbox = new Box3(bmin, bmax) @@ -304,6 +314,7 @@ const getBboxFromChunkId = (chunkId: ChunkId, patchSize: number) => { export { roundToDec, + vectRoundToDec, clamp, findMatchingRange, interpolatePoints, diff --git a/src/compute/WorldComputeApi.ts b/src/compute/WorldComputeApi.ts index 218c21f..53f81e2 100644 --- a/src/compute/WorldComputeApi.ts +++ b/src/compute/WorldComputeApi.ts @@ -2,7 +2,7 @@ import { Vector3 } from 'three' import { PatchKey } from '../common/types' import { BlockData, BlocksPatch } from '../data/DataContainers' -import { WorldCompute } from '../index' +import { WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { PatchCompute = 'computePatch', @@ -106,7 +106,11 @@ export class WorldComputeProxy implements ComputeApiInterface { ComputeApiCall.BlocksBatchCompute, [blockPosBatch, params], ) - return blockStubs as BlockData[] + const blocks = blockStubs.map((blockStub: BlockData) => { + blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos) + return blockStub + }) + return blocks as BlockData[] } async *iterPatchCompute(patchKeysBatch: PatchKey[]) { diff --git a/src/compute/world-compute.ts b/src/compute/world-compute.ts index e2b0fde..2d401c4 100644 --- a/src/compute/world-compute.ts +++ b/src/compute/world-compute.ts @@ -12,6 +12,7 @@ import { BlockData, BlocksContainer, BlocksPatch, + BlockStub, EntityChunk, } from '../data/DataContainers' import { PatchKey } from '../common/types' @@ -28,16 +29,20 @@ export const computeBlocksBatch = ( params = { includeEntitiesBlocks: false }, ) => { const blocksBatch = blockPosBatch.map(({ x, z }) => { - const block_pos = new Vector3(x, 0, z) - const block = computeGroundBlock(block_pos) + const blockPos = new Vector3(x, 0, z) + const blockStub = computeGroundBlock(blockPos) if (params.includeEntitiesBlocks) { - const blocksBuffer = computeBlocksBuffer(block_pos) + const blocksBuffer = computeBlocksBuffer(blockPos) const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) if (lastBlockIndex >= 0) { - block.pos.y += lastBlockIndex - block.type = blocksBuffer[lastBlockIndex] as BlockType + blockStub.level += lastBlockIndex + blockStub.type = blocksBuffer[lastBlockIndex] as BlockType } } + const block: BlockData = { + pos: blockPos, + type: blockStub.type + } return block }) return blocksBatch @@ -61,9 +66,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { // } // level += offset - const pos = blockPos.clone() - pos.y = level - const block: BlockData = { pos, type } + const block: BlockStub = { level, type } return block } @@ -162,11 +165,11 @@ const genGroundBlocks = (blocksContainer: BlocksContainer) => { for (const blockData of blocksPatchIter) { const blockPos = blockData.pos // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) - const block = computeGroundBlock(blockPos) - min.y = Math.min(min.y, block.pos.y) - max.y = Math.max(max.y, block.pos.y) + const blockRes = computeGroundBlock(blockPos) + min.y = Math.min(min.y, blockRes.level) + max.y = Math.max(max.y, blockRes.level) // blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) - blocksContainer.writeBlockAtIndex(blockIndex, block.pos.y, block.type) + blocksContainer.writeBlockAtIndex(blockIndex, blockRes.level, blockRes.type) blockIndex++ } blocksContainer.bbox.min = min diff --git a/src/config/WorldConfig.ts b/src/config/WorldConfig.ts new file mode 100644 index 0000000..1daadba --- /dev/null +++ b/src/config/WorldConfig.ts @@ -0,0 +1,11 @@ + + +export class WorldConfig { + static patchSize = Math.pow(2, 6) + static chunksYRange = { + min: 0, + max: 5 + } + // max cache radius as a power of two + static cachePowLimit = 2// 4 => 16 patches radius +} \ No newline at end of file diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index d72a1ae..b6de9a7 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,6 +1,7 @@ import { Vector3 } from 'three' import { vect3ToVect2 } from '../common/utils' +import { BlockType } from '../index' import { PatchContainer } from './DataContainers' @@ -14,7 +15,7 @@ export class BoardContainer extends PatchContainer { this.boardCenter = vect3ToVect2(center).floor() const board_dims = new Vector3(radius, 0, radius).multiplyScalar(2) this.bbox.setFromCenterAndSize(center.clone().floor(), board_dims) - this.init(this.bbox) + this.initFromBoxAndMask(this.bbox) } getMinMax() { @@ -47,7 +48,7 @@ export class BoardContainer extends PatchContainer { const dist = vect3ToVect2(block.pos).distanceTo(boardCenter) const y_diff = Math.abs(block.pos.y - avg) if (dist <= boardRadius && y_diff <= 5 && block.index !== undefined) { - patch.writeBlockAtIndex(block.index, avg, block.type) + patch.writeBlockAtIndex(block.index, block.pos.y, BlockType.MUD) } } }) diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index 6af61e9..4b54827 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -1,6 +1,7 @@ import { Box3, Vector3 } from 'three' import { PatchKey } from '../common/types' +import { WorldConfig } from '../config/WorldConfig' import { WorldComputeApi } from '../index' import { BlocksPatch, EntityChunk, PatchContainer } from './DataContainers' @@ -14,7 +15,7 @@ export class CacheContainer extends PatchContainer { pendingRefresh = false builtInCache = false // specify whether cache is managed internally or separately static cachePowRadius = 2 - static cacheSize = BlocksPatch.patchSize * 5 + static cacheSize = WorldConfig.patchSize * 5 // static worldApi = new WorldApi() // groundBlocks: Uint16Array = new Uint16Array(Math.pow(PatchBase.patchSize, 2)) @@ -46,11 +47,11 @@ export class CacheContainer extends PatchContainer { * @param dryRun * @returns true if cache was update, false otherwise */ - async refresh(bbox: Box3) { + async refresh(bbox: Box3){//, patchMask = () => true) { let changesDiff if (!this.pendingRefresh) { const emptyContainer = new PatchContainer() - emptyContainer.init(bbox) + emptyContainer.initFromBoxAndMask(bbox) changesDiff = emptyContainer.compareWith(CacheContainer.instance) const hasChanged = Object.keys(changesDiff).length > 0 @@ -59,7 +60,7 @@ export class CacheContainer extends PatchContainer { // backup patches that will remain in cache const backup = this.availablePatches.filter(patch => patch) // reinit cache - super.init(bbox) + super.initFromBoxAndMask(bbox) // restore remaining patches backup this.populateFromExisting(backup) this.builtInCache && (await this.populate(this.missingPatchKeys)) diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index c21baeb..fd77d4c 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -10,8 +10,9 @@ import { parseThreeStub, vect2ToVect3, } from '../common/utils' -import { ChunkTools } from '../index' import { BlockType } from '../procgen/Biome' +import { WorldConfig } from '../config/WorldConfig' +import { ChunkFactory } from '../index' export type BlockData = { pos: Vector3 @@ -107,7 +108,7 @@ export class BlocksContainer { } adaptCustomBox(bbox: Box3, useLocalPos = false) { - const { patchSize } = BlocksPatch + const { patchSize } = WorldConfig const bmin = new Vector3( Math.max(Math.floor(bbox.min.x), useLocalPos ? 0 : this.bbox.min.x), 0, @@ -218,7 +219,7 @@ export class BlocksContainer { } toChunk() { - return ChunkTools.makeChunkFromBox(this, this.bbox) + return ChunkFactory.makeChunkFromBox(this, this.bbox) } static fromStub(stub: any) { @@ -237,18 +238,13 @@ export class BlocksContainer { * Patch */ export class BlocksPatch extends BlocksContainer { - // eslint-disable-next-line no-use-before-define - // static cache: BlocksPatch[] = [] - static patchSize = Math.pow(2, 6) - static bbox = new Box3() - - coords: Vector2 + id: Vector2 key: string constructor(patchKey: string) { - super(getBboxFromPatchKey(patchKey, BlocksPatch.patchSize)) // .expandByScalar(1)) + super(getBboxFromPatchKey(patchKey)) // .expandByScalar(1)) this.key = patchKey - this.coords = parsePatchKey(patchKey) + this.id = parsePatchKey(patchKey) } override duplicate() { @@ -266,7 +262,7 @@ export class BlocksPatch extends BlocksContainer { const { groundBlocks, entitiesChunks } = patchStub const bbox = parseThreeStub(patchStub.bbox) const patchKey = - patchStub.key || computePatchKey(bbox, BlocksPatch.patchSize) + patchStub.key || computePatchKey(bbox) const patch = new BlocksPatch(patchKey) patch.groundBlocks = groundBlocks patch.entitiesChunks = entitiesChunks @@ -279,9 +275,9 @@ export class BlocksPatch extends BlocksContainer { } toChunks(yMin: number, yMax: number) { - const chunkIds = genChunkIds(this.coords, yMin, yMax) + const chunkIds = genChunkIds(this.id, yMin, yMax) const chunks = chunkIds.map(chunkId => - ChunkTools.makeChunkFromId(this, chunkId), + ChunkFactory.makeChunkFromId(this, chunkId), ) return chunks } @@ -292,9 +288,8 @@ export class PatchContainer { patchLookup: Record = {} get patchIdsRange() { - const { patchSize } = BlocksPatch - const rangeMin = convertPosToPatchId(this.bbox.min, patchSize) - const rangeMax = convertPosToPatchId(this.bbox.max, patchSize).addScalar(1) + const rangeMin = convertPosToPatchId(this.bbox.min) + const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) const patchIdsRange = new Box3( vect2ToVect3(rangeMin), vect2ToVect3(rangeMax), @@ -302,7 +297,7 @@ export class PatchContainer { return patchIdsRange } - init(bbox: Box3) { + initFromBoxAndMask(bbox: Box3, patchBboxMask = (patchBbox: Box3) => patchBbox) { this.bbox = bbox this.patchLookup = {} // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) @@ -312,8 +307,11 @@ export class PatchContainer { const { min, max } = this.patchIdsRange for (let { x } = min; x < max.x; x++) { for (let { z } = min; z < max.z; z++) { - const patchKey = 'patch_' + x + '_' + z - this.patchLookup[patchKey] = null + const patchKey = `${x}:${z}` + const patchBox = getBboxFromPatchKey(patchKey) + if (patchBboxMask(patchBox)) { + this.patchLookup[patchKey] = null + } } } } diff --git a/src/index.ts b/src/index.ts index 7bd90f4..7733b2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ + export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' @@ -14,9 +15,9 @@ export { CacheContainer as WorldCacheContainer } from './data/CacheContainer' export { WorldComputeApi } from './compute/WorldComputeApi' export * as WorldCompute from './compute/world-compute' export * as WorldUtils from './common/utils' -export * as ChunkTools from './utils/chunk_tools' -// export * as BoardUtils from './utils/BoardUtils' +export * as ChunkFactory from './tools/chunk-factory' export * as PlateauLegacy from './utils/plateau_legacy' +export { WorldConfig } from './config/WorldConfig' // export type { MappingConf, MappingData, MappingRanges } from "./common/types" // export { DevHacks } from './tools/DevHacks' diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index 26ae265..3683148 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -3,8 +3,7 @@ import alea from 'alea' import { Box3, Vector2, Vector3 } from 'three' import { TreeGenerators } from '../tools/TreeGenerator' -import { EntityType } from '../index' -import { BlocksPatch } from '../data/DataContainers' +import { EntityType, WorldConfig } from '../index' import { ProcLayer } from './ProcLayer' import { BlockType } from './Biome' @@ -158,7 +157,7 @@ export class RepeatableEntitiesMap extends EntitiesMap { period constructor() { super() - this.period = BlocksPatch.patchSize * 2 + this.period = WorldConfig.patchSize * 2 } // one time init of repeatable pattern diff --git a/src/utils/chunk_tools.ts b/src/tools/chunk-factory.ts similarity index 98% rename from src/utils/chunk_tools.ts rename to src/tools/chunk-factory.ts index eca02d4..3b945c2 100644 --- a/src/utils/chunk_tools.ts +++ b/src/tools/chunk-factory.ts @@ -2,7 +2,7 @@ import { Box3, MathUtils, Vector3 } from 'three' import { ChunkId, WorldChunk } from '../common/types' import { getBboxFromChunkId, serializeChunkId } from '../common/utils' -import { BlocksContainer, BlocksPatch, BlockType } from '../index' +import { BlocksContainer, BlockType, WorldConfig } from '../index' const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND @@ -124,7 +124,7 @@ export function makeChunkFromId( blocksContainer: BlocksContainer, chunkId: ChunkId, ) { - const chunkBox = getBboxFromChunkId(chunkId, BlocksPatch.patchSize) + const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) const chunk = makeChunkFromBox(blocksContainer, chunkBox) const regularChunk: WorldChunk = { key: serializeChunkId(chunkId), From 151e73af02324f661d31f482c24ab3864e133164 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 15 Aug 2024 21:44:50 +0000 Subject: [PATCH 10/45] fix: broken entities blocks buffer --- src/compute/world-compute.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compute/world-compute.ts b/src/compute/world-compute.ts index 2d401c4..aaa54c2 100644 --- a/src/compute/world-compute.ts +++ b/src/compute/world-compute.ts @@ -39,6 +39,7 @@ export const computeBlocksBatch = ( blockStub.type = blocksBuffer[lastBlockIndex] as BlockType } } + blockPos.y = blockStub.level const block: BlockData = { pos: blockPos, type: blockStub.type From e9103161ea8f20fbf94e0a223640fda028d9d0f7 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 16 Aug 2024 19:25:34 +0000 Subject: [PATCH 11/45] refactor: chunk gen misc improvements --- src/common/utils.ts | 36 ++++-- src/compute/world-compute.ts | 2 +- src/config/WorldConfig.ts | 15 +-- src/data/BoardContainer.ts | 8 +- src/data/CacheContainer.ts | 3 +- src/data/DataContainers.ts | 34 ++--- src/index.ts | 5 +- src/tools/ChunkFactory.ts | 214 +++++++++++++++++++++++++++++++ src/tools/chunk-factory.ts | 242 ----------------------------------- 9 files changed, 266 insertions(+), 293 deletions(-) create mode 100644 src/tools/ChunkFactory.ts delete mode 100644 src/tools/chunk-factory.ts diff --git a/src/common/utils.ts b/src/common/utils.ts index 64c032b..412988b 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,4 +1,5 @@ import { Box3, Vector2, Vector3, Vector3Like } from 'three' + import { WorldConfig } from '../config/WorldConfig' import { @@ -25,8 +26,10 @@ const vectRoundToDec = (input: Vector2 | Vector3, n_pow: number) => { let { x, y } = input x = roundToDec(x, n_pow) y = roundToDec(y, n_pow) - const output = input instanceof Vector3 ? new Vector3(x, y, roundToDec(input.z, n_pow)) : - new Vector2(x, y) + const output = + input instanceof Vector3 + ? new Vector3(x, y, roundToDec(input.z, n_pow)) + : new Vector2(x, y) return output } @@ -198,11 +201,11 @@ const bboxContainsPointXZ = (bbox: Box3, point: Vector3) => { ) } -const vect3ToVect2 = (v3: Vector3) => { +const asVect2 = (v3: Vector3) => { return new Vector2(v3.x, v3.z) } -const vect2ToVect3 = (v2: Vector2, yVal = 0) => { +const asVect3 = (v2: Vector2, yVal = 0) => { return new Vector3(v2.x, yVal, v2.y) } @@ -245,7 +248,10 @@ const parsePatchKey = (patchKey: PatchKey) => { return patchId } -const convertPosToPatchId = (position: Vector3, patchSize: number = WorldConfig.patchSize) => { +const convertPosToPatchId = ( + position: Vector3, + patchSize: number = WorldConfig.patchSize, +) => { const orig_x = Math.floor(position.x / patchSize) const orig_z = Math.floor(position.z / patchSize) const patchCoords = new Vector2(orig_x, orig_z) @@ -271,10 +277,13 @@ const computePatchKey = ( return patchKey } -const getBboxFromPatchKey = (patchKey: string, patchSize: number = WorldConfig.patchSize) => { +const getBboxFromPatchKey = ( + patchKey: string, + patchSize: number = WorldConfig.patchSize, +) => { const patchCoords = parsePatchKey(patchKey) - const bmin = vect2ToVect3(patchCoords.clone().multiplyScalar(patchSize)) - const bmax = vect2ToVect3( + const bmin = asVect3(patchCoords.clone().multiplyScalar(patchSize)) + const bmax = asVect3( patchCoords.clone().addScalar(1).multiplyScalar(patchSize), ) bmax.y = 512 @@ -298,13 +307,16 @@ const serializeChunkId = (chunkId: Vector3) => { function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { const chunk_ids = [] for (let y = ymax; y >= ymin; y--) { - const chunk_coords = vect2ToVect3(patchId, y) + const chunk_coords = asVect3(patchId, y) chunk_ids.push(chunk_coords) } return chunk_ids } -const getBboxFromChunkId = (chunkId: ChunkId, patchSize: number = WorldConfig.patchSize) => { +const getBboxFromChunkId = ( + chunkId: ChunkId, + patchSize: number = WorldConfig.patchSize, +) => { const bmin = chunkId.clone().multiplyScalar(patchSize) const bmax = chunkId.clone().addScalar(1).multiplyScalar(patchSize) const chunkBbox = new Box3(bmin, bmax) @@ -326,8 +338,8 @@ export { bboxContainsPointXZ, getPatchPoints, parseThreeStub, - vect2ToVect3, - vect3ToVect2, + asVect2, + asVect3, parsePatchKey, convertPosToPatchId, computePatchKey, diff --git a/src/compute/world-compute.ts b/src/compute/world-compute.ts index aaa54c2..d2f1051 100644 --- a/src/compute/world-compute.ts +++ b/src/compute/world-compute.ts @@ -42,7 +42,7 @@ export const computeBlocksBatch = ( blockPos.y = blockStub.level const block: BlockData = { pos: blockPos, - type: blockStub.type + type: blockStub.type, } return block }) diff --git a/src/config/WorldConfig.ts b/src/config/WorldConfig.ts index 1daadba..9598578 100644 --- a/src/config/WorldConfig.ts +++ b/src/config/WorldConfig.ts @@ -1,11 +1,6 @@ - - export class WorldConfig { - static patchSize = Math.pow(2, 6) - static chunksYRange = { - min: 0, - max: 5 - } - // max cache radius as a power of two - static cachePowLimit = 2// 4 => 16 patches radius -} \ No newline at end of file + static patchSize = Math.pow(2, 6) + + // max cache radius as a power of two + static cachePowLimit = 2 // 4 => 16 patches radius +} diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index b6de9a7..aa6b414 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,6 +1,6 @@ import { Vector3 } from 'three' -import { vect3ToVect2 } from '../common/utils' +import { asVect2 } from '../common/utils' import { BlockType } from '../index' import { PatchContainer } from './DataContainers' @@ -12,7 +12,7 @@ export class BoardContainer extends PatchContainer { constructor(center: Vector3, radius: number) { super() this.boardRadius = radius - this.boardCenter = vect3ToVect2(center).floor() + this.boardCenter = asVect2(center).floor() const board_dims = new Vector3(radius, 0, radius).multiplyScalar(2) this.bbox.setFromCenterAndSize(center.clone().floor(), board_dims) this.initFromBoxAndMask(this.bbox) @@ -26,7 +26,7 @@ export class BoardContainer extends PatchContainer { const blocks = patch.iterOverBlocks(this.bbox) for (const block of blocks) { // discard blocs not included in board shape - const dist = vect3ToVect2(block.pos).distanceTo(boardCenter) + const dist = asVect2(block.pos).distanceTo(boardCenter) if (dist <= boardRadius) { const block_level = block.pos.y ymin = Math.min(block_level, ymin) @@ -45,7 +45,7 @@ export class BoardContainer extends PatchContainer { const blocks = patch.iterOverBlocks(this.bbox) for (const block of blocks) { // discard blocs not included in board shape - const dist = vect3ToVect2(block.pos).distanceTo(boardCenter) + const dist = asVect2(block.pos).distanceTo(boardCenter) const y_diff = Math.abs(block.pos.y - avg) if (dist <= boardRadius && y_diff <= 5 && block.index !== undefined) { patch.writeBlockAtIndex(block.index, block.pos.y, BlockType.MUD) diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index 4b54827..8ceba92 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -47,7 +47,8 @@ export class CacheContainer extends PatchContainer { * @param dryRun * @returns true if cache was update, false otherwise */ - async refresh(bbox: Box3){//, patchMask = () => true) { + async refresh(bbox: Box3) { + //, patchMask = () => true) { let changesDiff if (!this.pendingRefresh) { const emptyContainer = new PatchContainer() diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index fd77d4c..9001273 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -2,13 +2,12 @@ import { Box3, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' import { + asVect3, computePatchKey, convertPosToPatchId, - genChunkIds, getBboxFromPatchKey, parsePatchKey, parseThreeStub, - vect2ToVect3, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' @@ -219,7 +218,7 @@ export class BlocksContainer { } toChunk() { - return ChunkFactory.makeChunkFromBox(this, this.bbox) + return ChunkFactory.default.makeChunkFromBox(this, this.bbox) } static fromStub(stub: any) { @@ -261,8 +260,7 @@ export class BlocksPatch extends BlocksContainer { static override fromStub(patchStub: any) { const { groundBlocks, entitiesChunks } = patchStub const bbox = parseThreeStub(patchStub.bbox) - const patchKey = - patchStub.key || computePatchKey(bbox) + const patchKey = patchStub.key || computePatchKey(bbox) const patch = new BlocksPatch(patchKey) patch.groundBlocks = groundBlocks patch.entitiesChunks = entitiesChunks @@ -274,12 +272,8 @@ export class BlocksPatch extends BlocksContainer { return patch } - toChunks(yMin: number, yMax: number) { - const chunkIds = genChunkIds(this.id, yMin, yMax) - const chunks = chunkIds.map(chunkId => - ChunkFactory.makeChunkFromId(this, chunkId), - ) - return chunks + toChunks() { + return ChunkFactory.default.genChunksFromPatch(this) } } @@ -290,14 +284,14 @@ export class PatchContainer { get patchIdsRange() { const rangeMin = convertPosToPatchId(this.bbox.min) const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) - const patchIdsRange = new Box3( - vect2ToVect3(rangeMin), - vect2ToVect3(rangeMax), - ) + const patchIdsRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) return patchIdsRange } - initFromBoxAndMask(bbox: Box3, patchBboxMask = (patchBbox: Box3) => patchBbox) { + initFromBoxAndMask( + bbox: Box3, + patchBboxMask = (patchBbox: Box3) => patchBbox, + ) { this.bbox = bbox this.patchLookup = {} // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) @@ -383,11 +377,11 @@ export class PatchContainer { return patchKeysDiff } - toChunks(yMin: number, yMax: number) { - const chunksExport = this.availablePatches - .map(patch => patch.toChunks(yMin, yMax)) + toChunks() { + const exportedChunks = this.availablePatches + .map(patch => patch.toChunks()) .flat() - return chunksExport + return exportedChunks } findPatch(blockPos: Vector3) { diff --git a/src/index.ts b/src/index.ts index 7733b2f..1f0412d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ - export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' @@ -13,11 +12,11 @@ export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' export { CacheContainer as WorldCacheContainer } from './data/CacheContainer' export { WorldComputeApi } from './compute/WorldComputeApi' +export { ChunkFactory } from './tools/ChunkFactory' +export { WorldConfig } from './config/WorldConfig' export * as WorldCompute from './compute/world-compute' export * as WorldUtils from './common/utils' -export * as ChunkFactory from './tools/chunk-factory' export * as PlateauLegacy from './utils/plateau_legacy' -export { WorldConfig } from './config/WorldConfig' // export type { MappingConf, MappingData, MappingRanges } from "./common/types" // export { DevHacks } from './tools/DevHacks' diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts new file mode 100644 index 0000000..8230592 --- /dev/null +++ b/src/tools/ChunkFactory.ts @@ -0,0 +1,214 @@ +import { Box3, MathUtils, Vector3 } from 'three' + +import { ChunkId, PatchId, WorldChunk } from '../common/types' +import { asVect3, getBboxFromChunkId, serializeChunkId } from '../common/utils' +import { BlocksContainer, BlocksPatch, BlockType, WorldConfig } from '../index' + +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE + +// for debug use only +const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { + return DBG_BORDERS_HIGHLIGHT_COLOR && (localPos.x === 1 || localPos.z === 1) + ? DBG_BORDERS_HIGHLIGHT_COLOR + : blockType +} + +export class ChunkFactory { + // eslint-disable-next-line no-use-before-define + static defaultInstance: ChunkFactory + voxelDataEncoder = (blockType: BlockType) => blockType || BlockType.NONE + chunksRange = { + ymin: 0, + ymax: 5, + } + + static get default() { + this.defaultInstance = this.defaultInstance || new ChunkFactory() + return this.defaultInstance + } + + setChunksGenRange(ymin: number, ymax: number) { + this.chunksRange.ymin = ymin + this.chunksRange.ymax = ymax + } + + writeChunkBlocks( + chunkData: Uint16Array, + chunkBbox: Box3, + blockLocalPos: Vector3, + groundType: BlockType, + bufferOver: any[] = [], + ) { + const chunk_size = Math.round(Math.pow(chunkData.length, 1 / 3)) + + let written_blocks_count = 0 + + const level = MathUtils.clamp( + blockLocalPos.y + bufferOver.length, + chunkBbox.min.y, + chunkBbox.max.y, + ) + let buff_index = Math.max(level - blockLocalPos.y, 0) + let h = level - chunkBbox.min.y // local height + // debug_mode && is_edge(local_pos.z, local_pos.x, h, patch_size - 2) + // ? BlockType.SAND + // : block_cache.type + + 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] : groundType + const skip = + buff_index > 0 && + chunkData[blocksIndex] !== undefined && + !bufferOver[buff_index] + if (!skip) { + chunkData[blocksIndex] = this.voxelDataEncoder(blockType) + blockType && written_blocks_count++ + } + buff_index-- + h-- + } + return written_blocks_count + } + + fillGroundData( + blocksContainer: BlocksContainer, + chunkData: Uint16Array, + chunkBox: Box3, + ) { + let written_blocks_count = 0 + const blocks_iter = blocksContainer.iterOverBlocks(undefined, true, false) + for (const block of blocks_iter) { + const blockLocalPos = block.pos + blockLocalPos.x += 1 + // blockLocalPos.y = patch.bbox.max.y + blockLocalPos.z += 1 + const blockType = + highlightPatchBorders(blockLocalPos, block.type) || block.type + written_blocks_count += this.writeChunkBlocks( + chunkData, + chunkBox, + blockLocalPos, + blockType, + ) + } + return written_blocks_count + } + + fillEntitiesData( + blocksContainer: BlocksContainer, + chunkData: Uint16Array, + chunkBox: Box3, + ) { + let written_blocks_count = 0 + // iter over container entities + for (const entity_chunk of blocksContainer.entitiesChunks) { + // const { min, max } = entity_chunk.bbox + // const bmin = new Vector3(...Object.values(min)) + // const bmax = new Vector3(...Object.values(max)) + // const entity_bbox = new Box3(bmin, bmax) + // find overlapping blocks between entity and container + const blocks_iter = blocksContainer.iterOverBlocks( + entity_chunk.bbox, + true, + ) + let chunk_index = 0 + // iter over entity blocks + for (const block of blocks_iter) { + const bufferStr = entity_chunk.data[chunk_index] + const buffer = + bufferStr && bufferStr.split(',').map(char => parseInt(char)) + if (buffer && block.localPos) { + block.buffer = buffer + block.localPos.x += 1 + block.localPos.z += 1 + // bmin.y = block.localPos.y + written_blocks_count += this.writeChunkBlocks( + chunkData, + chunkBox, + block.localPos, + block.type, + block.buffer, + ) + } + chunk_index++ + } + } + return written_blocks_count + } + + makeChunkFromBox(blocksContainer: BlocksContainer, chunkBox?: Box3) { + chunkBox = chunkBox || blocksContainer.bbox + const chunkDims = chunkBox.getSize(new Vector3()) + const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) + let totalWrittenBlocks = 0 + // const debug_mode = true + + // const is_edge = (row, col, h, patch_size) => + // row === 1 || row === patch_size || col === 1 || col === patch_size + // || h === 1 + // || h === patch_size - 2 + + // const patch = PatchBlocksCache.instances.find( + // patch => + // patch.bbox.min.x === bbox.min.x + 1 && + // patch.bbox.min.z === bbox.min.z + 1 && + // patch.bbox.max.x === bbox.max.x - 1 && + // patch.bbox.max.z === bbox.max.z - 1 && + // patch.bbox.intersectsBox(bbox), + // ) + + // multi-pass chunk filling + if (blocksContainer) { + // ground pass + totalWrittenBlocks += this.fillGroundData( + blocksContainer, + chunkData, + chunkBox, + ) + // overground entities pass + totalWrittenBlocks += this.fillEntitiesData( + blocksContainer, + chunkData, + chunkBox, + ) + } + // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) + // const dimensions = new Vector3(size, size, size) + const chunk = { + bbox: chunkBox, + data: totalWrittenBlocks ? chunkData : null, + // isEmpty: totalWrittenBlocks === 0, + } + return chunk + } + + makeChunkFromId(blocksContainer: BlocksContainer, chunkId: ChunkId) { + const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) + const chunk = this.makeChunkFromBox(blocksContainer, chunkBox) + const regularChunk: WorldChunk = { + key: serializeChunkId(chunkId), + data: chunk.data, + } + return regularChunk + } + + genChunksIdsFromPatchId(patchId: PatchId) { + const { ymin, ymax } = this.chunksRange + const chunk_ids = [] + for (let y = ymax; y >= ymin; y--) { + const chunk_coords = asVect3(patchId, y) + chunk_ids.push(chunk_coords) + } + return chunk_ids + } + + genChunksFromPatch(patch: BlocksPatch) { + const chunkIds = this.genChunksIdsFromPatchId(patch.id) + const chunks = chunkIds.map(chunkId => this.makeChunkFromId(patch, chunkId)) + return chunks + } +} diff --git a/src/tools/chunk-factory.ts b/src/tools/chunk-factory.ts deleted file mode 100644 index 3b945c2..0000000 --- a/src/tools/chunk-factory.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { Box3, MathUtils, Vector3 } from 'three' - -import { ChunkId, WorldChunk } from '../common/types' -import { getBboxFromChunkId, serializeChunkId } from '../common/utils' -import { BlocksContainer, BlockType, WorldConfig } from '../index' - -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND - -// for debug use only -const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return DBG_BORDERS_HIGHLIGHT_COLOR && (localPos.x === 1 || localPos.z === 1) - ? DBG_BORDERS_HIGHLIGHT_COLOR - : blockType -} - -const writeChunkBlocks = ( - chunkData: Uint16Array, - chunkBbox: Box3, - blockLocalPos: Vector3, - groundType: BlockType, - bufferOver: any[] = [], -) => { - const chunk_size = Math.round(Math.pow(chunkData.length, 1 / 3)) - - let written_blocks_count = 0 - - const level = MathUtils.clamp( - blockLocalPos.y + bufferOver.length, - chunkBbox.min.y, - chunkBbox.max.y, - ) - let buff_index = Math.max(level - blockLocalPos.y, 0) - let h = level - chunkBbox.min.y // local height - // debug_mode && is_edge(local_pos.z, local_pos.x, h, patch_size - 2) - // ? BlockType.SAND - // : block_cache.type - - 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] : groundType - const skip = - buff_index > 0 && - chunkData[blocksIndex] !== undefined && - !bufferOver[buff_index] - if (!skip) { - chunkData[blocksIndex] = blockType || BlockType.NONE - // ? voxelmapDataPacking.encode(false, blockType) - // : voxelmapDataPacking.encodeEmpty() - blockType && written_blocks_count++ - } - buff_index-- - h-- - } - return written_blocks_count -} - -const fillGroundData = ( - blocksContainer: BlocksContainer, - chunkData: Uint16Array, - chunkBox: Box3, -) => { - let written_blocks_count = 0 - const blocks_iter = blocksContainer.iterOverBlocks(undefined, true, false) - for (const block of blocks_iter) { - const blockLocalPos = block.pos - blockLocalPos.x += 1 - // blockLocalPos.y = patch.bbox.max.y - blockLocalPos.z += 1 - const blockType = - highlightPatchBorders(blockLocalPos, block.type) || block.type - written_blocks_count += writeChunkBlocks( - chunkData, - chunkBox, - blockLocalPos, - blockType, - ) - } - return written_blocks_count -} - -const fillEntitiesData = ( - blocksContainer: BlocksContainer, - chunkData: Uint16Array, - chunkBox: Box3, -) => { - let written_blocks_count = 0 - // iter over container entities - for (const entity_chunk of blocksContainer.entitiesChunks) { - // const { min, max } = entity_chunk.bbox - // const bmin = new Vector3(...Object.values(min)) - // const bmax = new Vector3(...Object.values(max)) - // const entity_bbox = new Box3(bmin, bmax) - // find overlapping blocks between entity and container - const blocks_iter = blocksContainer.iterOverBlocks(entity_chunk.bbox, true) - let chunk_index = 0 - // iter over entity blocks - for (const block of blocks_iter) { - const bufferStr = entity_chunk.data[chunk_index] - const buffer = - bufferStr && bufferStr.split(',').map(char => parseInt(char)) - if (buffer && block.localPos) { - block.buffer = buffer - block.localPos.x += 1 - block.localPos.z += 1 - // bmin.y = block.localPos.y - written_blocks_count += writeChunkBlocks( - chunkData, - chunkBox, - block.localPos, - block.type, - block.buffer, - ) - } - chunk_index++ - } - } - return written_blocks_count -} - -export function makeChunkFromId( - blocksContainer: BlocksContainer, - chunkId: ChunkId, -) { - const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) - const chunk = makeChunkFromBox(blocksContainer, chunkBox) - const regularChunk: WorldChunk = { - key: serializeChunkId(chunkId), - data: chunk.data, - } - return regularChunk -} - -export function makeChunkFromBox( - blocksContainer: BlocksContainer, - _chunkBox?: Box3, -) { - const chunkBox = _chunkBox || blocksContainer.bbox - const chunkDims = chunkBox.getSize(new Vector3()) - const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) - let totalWrittenBlocks = 0 - // const debug_mode = true - - // const is_edge = (row, col, h, patch_size) => - // row === 1 || row === patch_size || col === 1 || col === patch_size - // || h === 1 - // || h === patch_size - 2 - - // const patch = PatchBlocksCache.instances.find( - // patch => - // patch.bbox.min.x === bbox.min.x + 1 && - // patch.bbox.min.z === bbox.min.z + 1 && - // patch.bbox.max.x === bbox.max.x - 1 && - // patch.bbox.max.z === bbox.max.z - 1 && - // patch.bbox.intersectsBox(bbox), - // ) - - // multi-pass chunk filling - if (blocksContainer) { - // ground pass - totalWrittenBlocks += fillGroundData(blocksContainer, chunkData, chunkBox) - // overground entities pass - totalWrittenBlocks += fillEntitiesData(blocksContainer, chunkData, chunkBox) - } - // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) - // const dimensions = new Vector3(size, size, size) - const chunk = { - bbox: chunkBox, - data: totalWrittenBlocks ? chunkData : null, - // isEmpty: totalWrittenBlocks === 0, - } - return chunk -} - -// const plateau_ground_pass = (blocksContainer, chunk) => { -// const patch_center = blocksContainer.bbox.getCenter(new Vector3()) // patch.bbox.min.y -// const plateau_height = Math.floor(patch_center.y) -// const iter = blocksContainer.iterOverBlocks(undefined, true) -// let res = iter.next() -// while (!res.done) { -// const block_data = res.value -// const block_pos = block_data.pos.clone() -// block_pos.x += 1 -// block_pos.y = plateau_height -// block_pos.z += 1 -// const blockType = block_data.type -// writeChunkBlocks(chunk, block_pos, blockType) -// res = iter.next() -// } -// } - -// const buff_iter = patch_bis.overBlocksIter() -// for (const blk of buff_iter) { -// blk.localPos.x += 1 -// blk.localPos.z += 1 -// writeChunkBlocks(chunk, blk.localPos, blk.type, blk.buffer) -// } - -// export function get_plateau_chunks(plateau_keys) { -// const chunks = [] -// plateau_keys.forEach(patch_key => { -// const patch = WorldCache.patchLookupIndex[patch_key] -// const chunks_ids = genChunkIds(patch_key) -// patch && -// chunks_ids.forEach(chunk_coords => { -// let is_empty = true -// const bmin = chunk_coords.clone().multiplyScalar(world_patch_size) -// const bmax = chunk_coords -// .clone() -// .addScalar(1) -// .multiplyScalar(world_patch_size) -// const chunkBbox = new Box3(bmin, bmax) -// chunkBbox.expandByScalar(1) -// const dimensions = chunkBbox.getSize(new Vector3()) -// const data = new Uint16Array(dimensions.x * dimensions.y * dimensions.z) -// const chunk = { bbox: chunkBbox, data } - -// // multi-pass chunk filling -// if (patch) { -// // ground pass -// // ground_blocks_pass(patch, chunk) -// plateau_ground_pass(patch, chunk) -// // overground entities pass -// // plateau_entities_pass(patch, chunk) -// // // extra blocks at edges from adjacent patches -// edges_blocks_pass(chunk) -// is_empty = false -// } -// // fill_chunk_plateau_from_patch(patch, chunkBbox) -// const final_chunk = { -// id: chunk_coords, -// data, -// size: dimensions, -// isEmpty: false, -// } -// chunks.push(final_chunk) -// }) -// }) -// return chunks -// } From ddf34239248ebb4444593f6d453f28984d8aaacd Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 22 Aug 2024 10:05:05 +0000 Subject: [PATCH 12/45] feat: low-level data encoding/decoding for block storage --- src/{compute => api}/WorldComputeApi.ts | 13 +- src/{compute => api}/world-compute.ts | 19 ++- src/common/types.ts | 6 +- src/data/BoardContainer.ts | 26 ++-- src/data/CacheContainer.ts | 19 ++- src/data/DataContainers.ts | 151 +++++++++++++----------- src/index.ts | 5 +- src/tools/ChunkFactory.ts | 32 ++--- 8 files changed, 147 insertions(+), 124 deletions(-) rename src/{compute => api}/WorldComputeApi.ts (90%) rename src/{compute => api}/world-compute.ts (92%) diff --git a/src/compute/WorldComputeApi.ts b/src/api/WorldComputeApi.ts similarity index 90% rename from src/compute/WorldComputeApi.ts rename to src/api/WorldComputeApi.ts index 53f81e2..080aa3a 100644 --- a/src/compute/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -1,7 +1,7 @@ import { Vector3 } from 'three' -import { PatchKey } from '../common/types' -import { BlockData, BlocksPatch } from '../data/DataContainers' +import { Block, PatchKey } from '../common/types' +import { BlocksPatch } from '../data/DataContainers' import { WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { @@ -15,7 +15,7 @@ interface ComputeApiInterface { computeBlocksBatch( blockPosBatch: Vector3[], params?: any, - ): BlockData[] | Promise + ): Block[] | Promise // computePatch(patchKey: PatchKey): BlocksPatch | Promise iterPatchCompute( patchKeysBatch: PatchKey[], @@ -44,7 +44,7 @@ export class WorldComputeApi implements ComputeApiInterface { computeBlocksBatch( blockPosBatch: Vector3[], - params = { includeEntitiesBlocks: true }, + params: ComputeApiParams = { includeEntitiesBlocks: true }, ) { return WorldCompute.computeBlocksBatch(blockPosBatch, params) } @@ -106,11 +106,12 @@ export class WorldComputeProxy implements ComputeApiInterface { ComputeApiCall.BlocksBatchCompute, [blockPosBatch, params], ) - const blocks = blockStubs.map((blockStub: BlockData) => { + // parse worker's data to recreate original objects + const blocks: Block[] = blockStubs.map((blockStub: Block) => { blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos) return blockStub }) - return blocks as BlockData[] + return blocks } async *iterPatchCompute(patchKeysBatch: PatchKey[]) { diff --git a/src/compute/world-compute.ts b/src/api/world-compute.ts similarity index 92% rename from src/compute/world-compute.ts rename to src/api/world-compute.ts index d2f1051..422db21 100644 --- a/src/compute/world-compute.ts +++ b/src/api/world-compute.ts @@ -12,10 +12,9 @@ import { BlockData, BlocksContainer, BlocksPatch, - BlockStub, EntityChunk, } from '../data/DataContainers' -import { PatchKey } from '../common/types' +import { Block, PatchKey } from '../common/types' export const computePatch = (patchKey: PatchKey) => { const patch = new BlocksPatch(patchKey) @@ -40,7 +39,7 @@ export const computeBlocksBatch = ( } } blockPos.y = blockStub.level - const block: BlockData = { + const block: Block = { pos: blockPos, type: blockStub.type, } @@ -67,7 +66,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { // } // level += offset - const block: BlockStub = { level, type } + const block: BlockData = { level, type } return block } @@ -163,14 +162,12 @@ const genGroundBlocks = (blocksContainer: BlocksContainer) => { max.y = 0 let blockIndex = 0 - for (const blockData of blocksPatchIter) { - const blockPos = blockData.pos + for (const block of blocksPatchIter) { // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) - const blockRes = computeGroundBlock(blockPos) - min.y = Math.min(min.y, blockRes.level) - max.y = Math.max(max.y, blockRes.level) - // blocksContainer.writeBlockAtIndex(blockIndex, block.level, block.type) - blocksContainer.writeBlockAtIndex(blockIndex, blockRes.level, blockRes.type) + const blockData = computeGroundBlock(block.pos) + min.y = Math.min(min.y, blockData.level) + max.y = Math.max(max.y, blockData.level) + blocksContainer.writeBlockData(blockIndex, blockData) blockIndex++ } blocksContainer.bbox.min = min diff --git a/src/common/types.ts b/src/common/types.ts index 19d18d6..b3168e8 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,4 +1,5 @@ import { Vector2, Vector3 } from 'three' +import { BlockData } from '../data/DataContainers' import { BiomeType, BlockType } from '../procgen/Biome' @@ -6,7 +7,10 @@ import { LinkedList } from './misc' export type Block = { pos: Vector3 - type: BlockType + data: BlockData + index?: number + localPos?: Vector3 + buffer?: BlockType[] } export enum Adjacent2dPos { diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index aa6b414..387051a 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -3,7 +3,7 @@ import { Vector3 } from 'three' import { asVect2 } from '../common/utils' import { BlockType } from '../index' -import { PatchContainer } from './DataContainers' +import { BlockData, BlockMode, PatchContainer } from './DataContainers' export class BoardContainer extends PatchContainer { boardCenter @@ -12,9 +12,9 @@ export class BoardContainer extends PatchContainer { constructor(center: Vector3, radius: number) { super() this.boardRadius = radius - this.boardCenter = asVect2(center).floor() + this.boardCenter = center.clone().floor() const board_dims = new Vector3(radius, 0, radius).multiplyScalar(2) - this.bbox.setFromCenterAndSize(center.clone().floor(), board_dims) + this.bbox.setFromCenterAndSize(this.boardCenter, board_dims) this.initFromBoxAndMask(this.bbox) } @@ -26,7 +26,7 @@ export class BoardContainer extends PatchContainer { const blocks = patch.iterOverBlocks(this.bbox) for (const block of blocks) { // discard blocs not included in board shape - const dist = asVect2(block.pos).distanceTo(boardCenter) + const dist = asVect2(block.pos).distanceTo(asVect2(boardCenter)) if (dist <= boardRadius) { const block_level = block.pos.y ymin = Math.min(block_level, ymin) @@ -38,17 +38,21 @@ export class BoardContainer extends PatchContainer { } shapeBoard() { + const maxHeightDiff = 5 const { boardCenter, boardRadius } = this - const { ymin, ymax } = this.getMinMax() - const avg = Math.round(ymin + (ymax - ymin) / 2) + // const { ymin, ymax } = this.getMinMax() + // const avg = Math.round(ymin + (ymax - ymin) / 2) this.availablePatches.forEach(patch => { const blocks = patch.iterOverBlocks(this.bbox) for (const block of blocks) { // discard blocs not included in board shape - const dist = asVect2(block.pos).distanceTo(boardCenter) - const y_diff = Math.abs(block.pos.y - avg) - if (dist <= boardRadius && y_diff <= 5 && block.index !== undefined) { - patch.writeBlockAtIndex(block.index, block.pos.y, BlockType.MUD) + const dist = asVect2(block.pos).distanceTo(asVect2(boardCenter)) + const blockData = block.data + blockData.level = boardCenter.y + blockData.mode = BlockMode.BOARD_CONTAINER + const heightDiff = Math.abs(block.pos.y - boardCenter.y) + if (dist <= boardRadius && heightDiff <= maxHeightDiff && block.index !== undefined) { + patch.writeBlockData(block.index, blockData) } } }) @@ -71,5 +75,5 @@ export class BoardContainer extends PatchContainer { // }) // } - smoothEdges() {} + smoothEdges() { } } diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index 8ceba92..66cd88b 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -27,18 +27,15 @@ export class CacheContainer extends PatchContainer { return this.singleton } - async populate(batch: PatchKey[], dryRun = false) { - if (!dryRun && batch.length > 0) { - this.pendingRefresh = true - const batchIter = WorldComputeApi.instance.iterPatchCompute(batch) - // populate cache without blocking execution - for await (const patch of batchIter) { - this.patchLookup[patch.key] = patch - this.bbox.union(patch.bbox) - } - this.pendingRefresh = false + async populate(batch: PatchKey[]) { + this.pendingRefresh = true + const batchIter = WorldComputeApi.instance.iterPatchCompute(batch) + // populate cache without blocking execution + for await (const patch of batchIter) { + this.patchLookup[patch.key] = patch + this.bbox.union(patch.bbox) } - return batch + this.pendingRefresh = false } /** diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 9001273..3f306a8 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -1,6 +1,6 @@ import { Box3, Vector2, Vector3 } from 'three' -import { PatchKey } from '../common/types' +import { Block, PatchKey } from '../common/types' import { asVect3, computePatchKey, @@ -14,16 +14,14 @@ import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' export type BlockData = { - pos: Vector3 + level: number type: BlockType - index?: number - localPos?: Vector3 - buffer?: BlockType[] + mode?: BlockMode } -export type BlockStub = { - level: number - type: BlockType +export enum BlockMode { + DEFAULT, + BOARD_CONTAINER } export type EntityChunk = { @@ -34,14 +32,19 @@ export type EntityChunk = { export type PatchStub = { key: string bbox: Box3 - groundBlocks: { - type: Uint16Array - level: Uint16Array - } + groundBlocks: Uint32Array entitiesChunks: EntityChunk[] } -export type BlockIteratorRes = IteratorResult +// bits allocated per block data type +// total bits required to store a block: 9+10+3 = 22 bits +const BlockDataBitAllocation = { + level: 9, // support level values ranging from 0 to 512 + type: 10, // support up to 1024 different block types + mode: 3, // support for 8 different block mode +} + +export type BlockIteratorRes = IteratorResult /** * GenericBlocksContainer @@ -52,11 +55,7 @@ export class BlocksContainer { dimensions = new Vector3() margin = 0 - groundBlocks: { - type: Uint16Array - level: Uint16Array - } - + groundBlocks: Uint32Array entitiesChunks: EntityChunk[] = [] constructor(bbox: Box3, margin = 1) { @@ -64,30 +63,48 @@ export class BlocksContainer { this.bbox.getSize(this.dimensions) this.margin = margin const { extendedDims } = this - this.groundBlocks = { - type: new Uint16Array(extendedDims.x * extendedDims.z), - level: new Uint16Array(extendedDims.x * extendedDims.z), - } + this.groundBlocks = new Uint32Array(extendedDims.x * extendedDims.z) } duplicate() { const duplicate = new BlocksContainer(this.bbox) - this.groundBlocks.level.forEach( - (v, i) => (duplicate.groundBlocks.level[i] = v), - ) - this.groundBlocks.type.forEach( - (v, i) => (duplicate.groundBlocks.type[i] = v), + this.groundBlocks.forEach( + (v, i) => (duplicate.groundBlocks[i] = v), ) return duplicate } - writeBlockAtIndex( + decodeBlockData(rawData: number): BlockData { + const shift = BlockDataBitAllocation + const level = (rawData >> (shift.type + shift.mode)) & ((1 << shift.level) - 1); // Extract 9 bits for level + const type = (rawData >> shift.mode) & ((1 << shift.type) - 1); // Extract 10 bits for type + const mode = rawData & ((1 << shift.mode) - 1); // Extract 3 bits for mode + const blockData: BlockData = { + level, type, mode + } + return blockData + } + + encodeBlockData(blockData: BlockData): number { + const { level, type, mode } = blockData + const shift = BlockDataBitAllocation + let blockRawVal = level + blockRawVal = blockRawVal << shift.type | type + blockRawVal = blockRawVal << shift.mode | (mode || BlockMode.DEFAULT) + return blockRawVal + } + + readBlockData(blockIndex: number): BlockData { + const blockRawData = this.groundBlocks[blockIndex] + const blockData = this.decodeBlockData(blockRawData) + return blockData + } + + writeBlockData( blockIndex: number, - blockLevel: number, - blockType: BlockType, + blockData: BlockData ) { - this.groundBlocks.level[blockIndex] = blockLevel - this.groundBlocks.type[blockIndex] = blockType + this.groundBlocks[blockIndex] = this.encodeBlockData(blockData) } get extendedBox() { @@ -150,8 +167,8 @@ export class BlocksContainer { ) { const blockIndex = this.getBlockIndex(localPos) const pos = localPos.clone() - pos.y = this.groundBlocks.level[blockIndex] || 0 - const type = this.groundBlocks.type[blockIndex] + const { level, type } = this.readBlockData(blockIndex) + pos.y = level block = { pos, type, @@ -162,8 +179,11 @@ export class BlocksContainer { setBlock(localPos: Vector3, blockType: BlockType) { const blockIndex = localPos.x * this.dimensions.x + localPos.z - const blockLevel = localPos.y - this.writeBlockAtIndex(blockIndex, blockLevel, blockType) + const block = { + level: localPos.y, + type: blockType + } + this.writeBlockData(blockIndex, block) // const levelMax = blockLevel + blockData.over.length // bbox.min.y = Math.min(bbox.min.y, levelMax) // bbox.max.y = Math.max(bbox.max.y, levelMax) @@ -191,17 +211,16 @@ export class BlocksContainer { if (!skipMargin || !isMarginBlock(pos)) { const localPos = useLocalPos ? pos : this.getLocalPos(pos) index = customBox ? this.getBlockIndex(localPos) : index - const type = this.groundBlocks.type[index] || BlockType.NONE - const level = this.groundBlocks.level[index] || 0 - pos.y = level - localPos.y = level - const blockData: BlockData = { + const blockData = this.readBlockData(index) || BlockType.NONE + pos.y = blockData.level + localPos.y = blockData.level + const block: Block = { index, pos, localPos, - type, + data: blockData, } - yield blockData + yield block } index++ } @@ -247,14 +266,9 @@ export class BlocksPatch extends BlocksContainer { } override duplicate() { - const duplicate = new BlocksPatch(this.key) - this.groundBlocks.level.forEach( - (v, i) => (duplicate.groundBlocks.level[i] = v), - ) - this.groundBlocks.type.forEach( - (v, i) => (duplicate.groundBlocks.type[i] = v), - ) - return duplicate + const copy = new BlocksPatch(this.key) + this.groundBlocks.forEach((rawVal, i) => copy.groundBlocks[i] = rawVal) + return copy } static override fromStub(patchStub: any) { @@ -345,23 +359,23 @@ export class PatchContainer { } mergeBlocks(blocksContainer: BlocksContainer) { - // for each patch override with blocks from blocks container - this.availablePatches.forEach(patch => { - const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) - for (const target_block of blocksIter) { - const source_block = blocksContainer.getBlock(target_block.pos, false) - if (source_block && source_block.pos.y > 0 && target_block.index) { - let block_type = source_block.type ? BlockType.SAND : BlockType.NONE - block_type = - source_block.type === BlockType.TREE_TRUNK - ? BlockType.TREE_TRUNK - : block_type - const block_level = blocksContainer.bbox.min.y // source_block?.pos.y - patch.writeBlockAtIndex(target_block.index, block_level, block_type) - // console.log(source_block?.pos.y) - } - } - }) + // // for each patch override with blocks from blocks container + // this.availablePatches.forEach(patch => { + // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + // for (const target_block of blocksIter) { + // const source_block = blocksContainer.getBlock(target_block.pos, false) + // if (source_block && source_block.pos.y > 0 && target_block.index) { + // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE + // block_type = + // source_block.type === BlockType.TREE_TRUNK + // ? BlockType.TREE_TRUNK + // : block_type + // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y + // patch.writeBlock(target_block.index, block_level, block_type) + // // console.log(source_block?.pos.y) + // } + // } + // }) } compareWith(otherContainer: PatchContainer) { @@ -397,3 +411,4 @@ export class PatchContainer { return res } } + diff --git a/src/index.ts b/src/index.ts index 1f0412d..460f1f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,16 +5,17 @@ export { BlocksContainer, BlocksPatch, PatchContainer, + BlockMode } from './data/DataContainers' export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' export { CacheContainer as WorldCacheContainer } from './data/CacheContainer' -export { WorldComputeApi } from './compute/WorldComputeApi' export { ChunkFactory } from './tools/ChunkFactory' export { WorldConfig } from './config/WorldConfig' -export * as WorldCompute from './compute/world-compute' +export { WorldComputeApi } from './api/WorldComputeApi' +export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' export * as PlateauLegacy from './utils/plateau_legacy' diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 8230592..49a2481 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -2,7 +2,8 @@ import { Box3, MathUtils, Vector3 } from 'three' import { ChunkId, PatchId, WorldChunk } from '../common/types' import { asVect3, getBboxFromChunkId, serializeChunkId } from '../common/utils' -import { BlocksContainer, BlocksPatch, BlockType, WorldConfig } from '../index' +import { BlockData } from '../data/DataContainers' +import { BlockMode, BlocksContainer, BlocksPatch, BlockType, WorldConfig } from '../index' const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE @@ -16,7 +17,7 @@ const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { export class ChunkFactory { // eslint-disable-next-line no-use-before-define static defaultInstance: ChunkFactory - voxelDataEncoder = (blockType: BlockType) => blockType || BlockType.NONE + voxelDataEncoder = (blockType: BlockType, blockMode?: BlockMode) => blockType || BlockType.NONE chunksRange = { ymin: 0, ymax: 5, @@ -33,13 +34,13 @@ export class ChunkFactory { } writeChunkBlocks( - chunkData: Uint16Array, + chunkDataContainer: Uint16Array, chunkBbox: Box3, blockLocalPos: Vector3, - groundType: BlockType, + blockData: BlockData, bufferOver: any[] = [], ) { - const chunk_size = Math.round(Math.pow(chunkData.length, 1 / 3)) + const chunk_size = chunkBbox.getSize(new Vector3()).x //Math.round(Math.pow(chunkDataContainer.length, 1 / 3)) let written_blocks_count = 0 @@ -59,13 +60,13 @@ export class ChunkFactory { blockLocalPos.z * Math.pow(chunk_size, 2) + h * chunk_size + blockLocalPos.x - const blockType = buff_index > 0 ? bufferOver[buff_index] : groundType + const blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type const skip = buff_index > 0 && - chunkData[blocksIndex] !== undefined && + chunkDataContainer[blocksIndex] !== undefined && !bufferOver[buff_index] if (!skip) { - chunkData[blocksIndex] = this.voxelDataEncoder(blockType) + chunkDataContainer[blocksIndex] = this.voxelDataEncoder(blockType, blockData.mode) blockType && written_blocks_count++ } buff_index-- @@ -76,23 +77,26 @@ export class ChunkFactory { fillGroundData( blocksContainer: BlocksContainer, - chunkData: Uint16Array, + chunkDataContainer: Uint16Array, chunkBox: Box3, ) { let written_blocks_count = 0 const blocks_iter = blocksContainer.iterOverBlocks(undefined, true, false) for (const block of blocks_iter) { const blockLocalPos = block.pos + const blockData = block.data + const blockType = block.data.type blockLocalPos.x += 1 // blockLocalPos.y = patch.bbox.max.y blockLocalPos.z += 1 - const blockType = - highlightPatchBorders(blockLocalPos, block.type) || block.type + blockData.type = + highlightPatchBorders(blockLocalPos, blockType) || blockType written_blocks_count += this.writeChunkBlocks( - chunkData, + chunkDataContainer, chunkBox, blockLocalPos, - blockType, + blockData, + block.buffer ) } return written_blocks_count @@ -130,7 +134,7 @@ export class ChunkFactory { chunkData, chunkBox, block.localPos, - block.type, + block.data, block.buffer, ) } From bb8130851838f04ad2662def6e8f2b422167d153 Mon Sep 17 00:00:00 2001 From: etienne Date: Sun, 25 Aug 2024 12:16:02 +0000 Subject: [PATCH 13/45] feat: chunk making refactor, entities blocks iteration --- src/data/BoardContainer.ts | 23 ++++- src/data/DataContainers.ts | 179 +++++++++++++++++++++++++++++++++---- src/tools/ChunkFactory.ts | 121 +++++-------------------- 3 files changed, 203 insertions(+), 120 deletions(-) diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index 387051a..f56455a 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,14 +1,14 @@ import { Vector3 } from 'three' import { asVect2 } from '../common/utils' -import { BlockType } from '../index' -import { BlockData, BlockMode, PatchContainer } from './DataContainers' +import { BlockMode, EntityChunk, PatchContainer } from './DataContainers' export class BoardContainer extends PatchContainer { boardCenter boardRadius - + innerEntities: EntityChunk[] = [] + outerEntities: EntityChunk[] = [] constructor(center: Vector3, radius: number) { super() this.boardRadius = radius @@ -25,7 +25,7 @@ export class BoardContainer extends PatchContainer { this.availablePatches.forEach(patch => { const blocks = patch.iterOverBlocks(this.bbox) for (const block of blocks) { - // discard blocs not included in board shape + // discard blocks not included in board shape const dist = asVect2(block.pos).distanceTo(asVect2(boardCenter)) if (dist <= boardRadius) { const block_level = block.pos.y @@ -58,6 +58,21 @@ export class BoardContainer extends PatchContainer { }) } + sortEntities() { + this.availablePatches.forEach(patch => { + patch.entitiesChunks.forEach(entity => { + const localCenter = entity.bbox.getCenter(new Vector3()) + const entityCenter = patch.toGlobalPos(localCenter) + const isWithinBoard = this.bbox.containsPoint(entityCenter) + isWithinBoard ? this.innerEntities.push(entity) : this.outerEntities.push(entity) + }) + }) + } + + trimEntities(){ + this.sortEntities() + } + // mergeBoardBlocks(blocksContainer: BlocksContainer) { // // for each patch override with blocks from blocks container // this.availablePatches.forEach(patch => { diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 3f306a8..76723bc 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -1,13 +1,15 @@ import { Box3, Vector2, Vector3 } from 'three' -import { Block, PatchKey } from '../common/types' +import { Block, PatchKey, WorldChunk } from '../common/types' import { asVect3, computePatchKey, convertPosToPatchId, + getBboxFromChunkId, getBboxFromPatchKey, parsePatchKey, parseThreeStub, + serializeChunkId, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' @@ -67,11 +69,12 @@ export class BlocksContainer { } duplicate() { - const duplicate = new BlocksContainer(this.bbox) + const copy = new BlocksContainer(this.bbox) this.groundBlocks.forEach( - (v, i) => (duplicate.groundBlocks[i] = v), + (v, i) => (copy.groundBlocks[i] = v), ) - return duplicate + copy.entitiesChunks = this.entitiesChunks + return copy } decodeBlockData(rawData: number): BlockData { @@ -152,12 +155,16 @@ export class BlocksContainer { ) } - getLocalPos(pos: Vector3) { + toLocalPos(pos: Vector3) { return pos.clone().sub(this.bbox.min) } + toGlobalPos(pos: Vector3) { + return this.bbox.min.clone().add(pos) + } + getBlock(pos: Vector3, useLocalPos = true) { - const localPos = useLocalPos ? pos : this.getLocalPos(pos) + const localPos = useLocalPos ? pos : this.toLocalPos(pos) let block if ( localPos.x >= 0 && @@ -189,6 +196,17 @@ export class BlocksContainer { // bbox.max.y = Math.max(bbox.max.y, levelMax) } + getBlocksRow(zRowIndex: number) { + const rowStart = zRowIndex * this.dimensions.z + const rowEnd = rowStart + this.dimensions.x + const rowRawData = this.groundBlocks.slice(rowStart, rowEnd) + return rowRawData + } + + getBlocksCol(xColIndex: number) { + + } + *iterOverBlocks(customBox?: Box3, useLocalPos = false, skipMargin = true) { const bbox = customBox ? this.adaptCustomBox(customBox, useLocalPos) @@ -209,7 +227,7 @@ export class BlocksContainer { for (let { z } = bbox.min; z < bbox.max.z; z++) { const pos = new Vector3(x, 0, z) if (!skipMargin || !isMarginBlock(pos)) { - const localPos = useLocalPos ? pos : this.getLocalPos(pos) + const localPos = useLocalPos ? pos : this.toLocalPos(pos) index = customBox ? this.getBlockIndex(localPos) : index const blockData = this.readBlockData(index) || BlockType.NONE pos.y = blockData.level @@ -227,6 +245,26 @@ export class BlocksContainer { } } + *iterEntityBlocks(entity: EntityChunk) { + + // find overlapping blocks between entity and container + const blocks_iter = this.iterOverBlocks( + entity.bbox, + true, + ) + let chunk_index = 0 + // iter over entity blocks + for (const block of blocks_iter) { + const bufferStr = entity.data[chunk_index++] + const buffer = + bufferStr && bufferStr.split(',').map(char => parseInt(char)) + const entityBlockData = block + entityBlockData.buffer = buffer || [] + yield entityBlockData + } + // } + } + containsBlock(blockPos: Vector3) { return ( blockPos.x >= this.bbox.min.x && @@ -236,8 +274,55 @@ export class BlocksContainer { ) } - toChunk() { - return ChunkFactory.default.makeChunkFromBox(this, this.bbox) + toChunk(chunkBox: Box3) { + chunkBox = chunkBox || this.bbox + const chunkDims = chunkBox.getSize(new Vector3()) + const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) + let totalWrittenBlocks = 0 + // const debug_mode = true + + // const is_edge = (row, col, h, patch_size) => + // row === 1 || row === patch_size || col === 1 || col === patch_size + // || h === 1 + // || h === patch_size - 2 + + // const patch = PatchBlocksCache.instances.find( + // patch => + // patch.bbox.min.x === bbox.min.x + 1 && + // patch.bbox.min.z === bbox.min.z + 1 && + // patch.bbox.max.x === bbox.max.x - 1 && + // patch.bbox.max.z === bbox.max.z - 1 && + // patch.bbox.intersectsBox(bbox), + // ) + + // multi-pass chunk filling + + const blockIterator = this.iterOverBlocks(undefined, true, false) + // ground blocks pass + totalWrittenBlocks += ChunkFactory.default.fillGroundData( + blockIterator, + chunkData, + chunkBox, + ) + // entities blocks pass + for (const entity of this.entitiesChunks) { + const entityBlocksIterator = this.iterEntityBlocks(entity) + // overground entities pass + totalWrittenBlocks += ChunkFactory.default.fillEntitiesData( + entityBlocksIterator, + chunkData, + chunkBox, + ) + } + + // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) + // const dimensions = new Vector3(size, size, size) + const chunk = { + bbox: chunkBox, + data: totalWrittenBlocks ? chunkData : null, + // isEmpty: totalWrittenBlocks === 0, + } + return chunk } static fromStub(stub: any) { @@ -268,6 +353,7 @@ export class BlocksPatch extends BlocksContainer { override duplicate() { const copy = new BlocksPatch(this.key) this.groundBlocks.forEach((rawVal, i) => copy.groundBlocks[i] = rawVal) + copy.entitiesChunks = this.entitiesChunks return copy } @@ -277,7 +363,13 @@ export class BlocksPatch extends BlocksContainer { const patchKey = patchStub.key || computePatchKey(bbox) const patch = new BlocksPatch(patchKey) patch.groundBlocks = groundBlocks - patch.entitiesChunks = entitiesChunks + patch.entitiesChunks = entitiesChunks.map((stub: EntityChunk) => { + const entityChunk: EntityChunk = { + bbox: parseThreeStub(stub.bbox), + data: stub.data + } + return entityChunk + }) patch.bbox.min.y = patchStub.bbox.min.y patch.bbox.max.y = patchStub.bbox.max.y // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => @@ -287,7 +379,17 @@ export class BlocksPatch extends BlocksContainer { } toChunks() { - return ChunkFactory.default.genChunksFromPatch(this) + const chunkIds = ChunkFactory.default.genChunksIdsFromPatchId(this.id) + const chunks = chunkIds.map(chunkId => { + const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) + const chunk = super.toChunk(chunkBox) + const worldChunk: WorldChunk = { + key: serializeChunkId(chunkId), + data: chunk.data, + } + return worldChunk + }) + return chunks } } @@ -295,11 +397,11 @@ export class PatchContainer { bbox: Box3 = new Box3() patchLookup: Record = {} - get patchIdsRange() { + get patchRange() { const rangeMin = convertPosToPatchId(this.bbox.min) const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) - const patchIdsRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) - return patchIdsRange + const patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) + return patchRange } initFromBoxAndMask( @@ -312,7 +414,7 @@ export class PatchContainer { // const range = BlocksPatch.asPatchCoords(halfDimensions) // const center = this.bbox.getCenter(new Vector3()) // const origin = BlocksPatch.asPatchCoords(center) - const { min, max } = this.patchIdsRange + const { min, max } = this.patchRange for (let { x } = min; x < max.x; x++) { for (let { z } = min; z < max.z; z++) { const patchKey = `${x}:${z}` @@ -391,6 +493,53 @@ export class PatchContainer { return patchKeysDiff } + getMergedRows(zRowIndex: number) { + const sortedPatchesRows = this.availablePatches + .filter(patch => zRowIndex >= patch.bbox.min.z && zRowIndex <= patch.bbox.min.z) + .sort((p1, p2) => p1.bbox.min.x - p2.bbox.min.x) + .map(patch => patch.getBlocksRow(zRowIndex)) + const mergedRows = sortedPatchesRows.reduce((arr1, arr2) => { + const mergedArray = new Uint32Array(arr1.length + arr2.length) + mergedArray.set(arr1) + mergedArray.set(arr2, arr1.length) + return mergedArray + }) + return mergedRows + } + + iterMergedRows() { + const { min, max } = this.patchRange + for (let zPatchIndex = min.z; zPatchIndex <= max.z; zPatchIndex++) { + for (let zRowIndex = min.z; zRowIndex < max.z; zRowIndex++) { + + } + } + } + + getMergedCols(xColIndex: number) { + + } + + mergedLinesIteration() { + const { min, max } = this.bbox + for (let x = min.x; x < max.x; x++) { + for (let z = min.z; z < max.z; z++) { + + } + } + } + + toMergedContainer() { + const mergedBox = this.availablePatches.map(patch => patch.bbox) + .reduce((merge, bbox) => merge.union(bbox), new Box3()) + // const mergedContainer = + } + + static fromMergedContainer() { + + } + + toChunks() { const exportedChunks = this.availablePatches .map(patch => patch.toChunks()) diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 49a2481..1da5d2a 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,11 +1,11 @@ import { Box3, MathUtils, Vector3 } from 'three' -import { ChunkId, PatchId, WorldChunk } from '../common/types' +import { Block, ChunkId, PatchId, WorldChunk } from '../common/types' import { asVect3, getBboxFromChunkId, serializeChunkId } from '../common/utils' import { BlockData } from '../data/DataContainers' import { BlockMode, BlocksContainer, BlocksPatch, BlockType, WorldConfig } from '../index' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND // disabled if NONE // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -76,13 +76,12 @@ export class ChunkFactory { } fillGroundData( - blocksContainer: BlocksContainer, + blockIterator: Generator, chunkDataContainer: Uint16Array, chunkBox: Box3, ) { let written_blocks_count = 0 - const blocks_iter = blocksContainer.iterOverBlocks(undefined, true, false) - for (const block of blocks_iter) { + for (const block of blockIterator) { const blockLocalPos = block.pos const blockData = block.data const blockType = block.data.type @@ -103,101 +102,27 @@ export class ChunkFactory { } fillEntitiesData( - blocksContainer: BlocksContainer, + entityBlocksIterator: Generator, chunkData: Uint16Array, chunkBox: Box3, ) { - let written_blocks_count = 0 - // iter over container entities - for (const entity_chunk of blocksContainer.entitiesChunks) { - // const { min, max } = entity_chunk.bbox - // const bmin = new Vector3(...Object.values(min)) - // const bmax = new Vector3(...Object.values(max)) - // const entity_bbox = new Box3(bmin, bmax) - // find overlapping blocks between entity and container - const blocks_iter = blocksContainer.iterOverBlocks( - entity_chunk.bbox, - true, - ) - let chunk_index = 0 - // iter over entity blocks - for (const block of blocks_iter) { - const bufferStr = entity_chunk.data[chunk_index] - const buffer = - bufferStr && bufferStr.split(',').map(char => parseInt(char)) - if (buffer && block.localPos) { - block.buffer = buffer - block.localPos.x += 1 - block.localPos.z += 1 - // bmin.y = block.localPos.y - written_blocks_count += this.writeChunkBlocks( - chunkData, - chunkBox, - block.localPos, - block.data, - block.buffer, - ) - } - chunk_index++ + let writtenBlocksCount = 0 + // iter over entity blocks + for (const entityBlock of entityBlocksIterator) { + if (entityBlock.buffer && entityBlock.localPos) { + entityBlock.localPos.x += 1 + entityBlock.localPos.z += 1 + // bmin.y = block.localPos.y + writtenBlocksCount += this.writeChunkBlocks( + chunkData, + chunkBox, + entityBlock.localPos, + entityBlock.data, + entityBlock.buffer, + ) } } - return written_blocks_count - } - - makeChunkFromBox(blocksContainer: BlocksContainer, chunkBox?: Box3) { - chunkBox = chunkBox || blocksContainer.bbox - const chunkDims = chunkBox.getSize(new Vector3()) - const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) - let totalWrittenBlocks = 0 - // const debug_mode = true - - // const is_edge = (row, col, h, patch_size) => - // row === 1 || row === patch_size || col === 1 || col === patch_size - // || h === 1 - // || h === patch_size - 2 - - // const patch = PatchBlocksCache.instances.find( - // patch => - // patch.bbox.min.x === bbox.min.x + 1 && - // patch.bbox.min.z === bbox.min.z + 1 && - // patch.bbox.max.x === bbox.max.x - 1 && - // patch.bbox.max.z === bbox.max.z - 1 && - // patch.bbox.intersectsBox(bbox), - // ) - - // multi-pass chunk filling - if (blocksContainer) { - // ground pass - totalWrittenBlocks += this.fillGroundData( - blocksContainer, - chunkData, - chunkBox, - ) - // overground entities pass - totalWrittenBlocks += this.fillEntitiesData( - blocksContainer, - chunkData, - chunkBox, - ) - } - // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) - // const dimensions = new Vector3(size, size, size) - const chunk = { - bbox: chunkBox, - data: totalWrittenBlocks ? chunkData : null, - // isEmpty: totalWrittenBlocks === 0, - } - return chunk - } - - makeChunkFromId(blocksContainer: BlocksContainer, chunkId: ChunkId) { - const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) - const chunk = this.makeChunkFromBox(blocksContainer, chunkBox) - const regularChunk: WorldChunk = { - key: serializeChunkId(chunkId), - data: chunk.data, - } - return regularChunk + return writtenBlocksCount } genChunksIdsFromPatchId(patchId: PatchId) { @@ -209,10 +134,4 @@ export class ChunkFactory { } return chunk_ids } - - genChunksFromPatch(patch: BlocksPatch) { - const chunkIds = this.genChunksIdsFromPatchId(patch.id) - const chunks = chunkIds.map(chunkId => this.makeChunkFromId(patch, chunkId)) - return chunks - } } From 1c6fd6ac09beb443ec478009b78b0c5b52e8dc10 Mon Sep 17 00:00:00 2001 From: etienne Date: Sun, 25 Aug 2024 12:23:26 +0000 Subject: [PATCH 14/45] refactor: local/global pos, --- src/api/world-compute.ts | 6 +- src/data/DataContainers.ts | 156 ++++++++++++++++++++----------------- src/tools/ChunkFactory.ts | 11 ++- 3 files changed, 94 insertions(+), 79 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 422db21..c30d16c 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -99,16 +99,16 @@ const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { } const blocksIter = patch.iterOverBlocks(entity.bbox, true) for (const block of blocksIter) { - const blocksBuffer = EntitiesMap.fillBlockBuffer(block.pos, entity, []) + const blocksBuffer = EntitiesMap.fillBlockBuffer(block.localPos, entity, []) patch.bbox.max.y = Math.max( patch.bbox.max.y, - block.pos.y + blocksBuffer.length, + block.localPos.y + blocksBuffer.length, ) const serialized = blocksBuffer .reduce((str, val) => str + ',' + val, '') .slice(1) entityChunk.data.push(serialized) - entityChunk.bbox.expandByPoint(block.pos) + entityChunk.bbox.expandByPoint(block.localPos) } entityChunk.bbox = entity.bbox return entityChunk diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 76723bc..a7dbdd8 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -163,9 +163,9 @@ export class BlocksContainer { return this.bbox.min.clone().add(pos) } - getBlock(pos: Vector3, useLocalPos = true) { - const localPos = useLocalPos ? pos : this.toLocalPos(pos) - let block + getBlock(pos: Vector3, isLocalPos = true) { + const localPos = isLocalPos ? pos : this.toLocalPos(pos) + let block: Block | undefined if ( localPos.x >= 0 && localPos.x < this.dimensions.x && @@ -173,12 +173,12 @@ export class BlocksContainer { localPos.z < this.dimensions.z ) { const blockIndex = this.getBlockIndex(localPos) - const pos = localPos.clone() - const { level, type } = this.readBlockData(blockIndex) - pos.y = level + const pos = isLocalPos ? localPos.clone() : this.toGlobalPos(localPos) + const data = this.readBlockData(blockIndex) + pos.y = data.level block = { pos, - type, + data } } return block @@ -234,7 +234,7 @@ export class BlocksContainer { localPos.y = blockData.level const block: Block = { index, - pos, + pos: useLocalPos ? this.toGlobalPos(pos) : pos, localPos, data: blockData, } @@ -246,7 +246,6 @@ export class BlocksContainer { } *iterEntityBlocks(entity: EntityChunk) { - // find overlapping blocks between entity and container const blocks_iter = this.iterOverBlocks( entity.bbox, @@ -256,10 +255,10 @@ export class BlocksContainer { // iter over entity blocks for (const block of blocks_iter) { const bufferStr = entity.data[chunk_index++] - const buffer = - bufferStr && bufferStr.split(',').map(char => parseInt(char)) + const buffer = bufferStr?.split(',').map(char => parseInt(char)) + const maxHeightDiff = entity.bbox.max.y - block.localPos.y const entityBlockData = block - entityBlockData.buffer = buffer || [] + entityBlockData.buffer = buffer?.slice(0, maxHeightDiff) || [] yield entityBlockData } // } @@ -353,7 +352,13 @@ export class BlocksPatch extends BlocksContainer { override duplicate() { const copy = new BlocksPatch(this.key) this.groundBlocks.forEach((rawVal, i) => copy.groundBlocks[i] = rawVal) - copy.entitiesChunks = this.entitiesChunks + copy.entitiesChunks = this.entitiesChunks.map(entity => { + const entityCopy: EntityChunk = { + bbox: entity.bbox.clone(), + data: entity.data.slice() + } + return entityCopy + }) return copy } @@ -378,9 +383,12 @@ export class BlocksPatch extends BlocksContainer { return patch } + get chunkIds() { + return ChunkFactory.default.genChunksIdsFromPatchId(this.id) + } + toChunks() { - const chunkIds = ChunkFactory.default.genChunksIdsFromPatchId(this.id) - const chunks = chunkIds.map(chunkId => { + const chunks = this.chunkIds.map(chunkId => { const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) const chunk = super.toChunk(chunkBox) const worldChunk: WorldChunk = { @@ -397,13 +405,6 @@ export class PatchContainer { bbox: Box3 = new Box3() patchLookup: Record = {} - get patchRange() { - const rangeMin = convertPosToPatchId(this.bbox.min) - const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) - const patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) - return patchRange - } - initFromBoxAndMask( bbox: Box3, patchBboxMask = (patchBbox: Box3) => patchBbox, @@ -426,14 +427,11 @@ export class PatchContainer { } } - get availablePatches() { - return Object.values(this.patchLookup).filter(val => val) as BlocksPatch[] - } - - get missingPatchKeys() { - return Object.keys(this.patchLookup).filter( - key => !this.patchLookup[key], - ) as PatchKey[] + get patchRange() { + const rangeMin = convertPosToPatchId(this.bbox.min) + const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) + const patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) + return patchRange } get count() { @@ -444,6 +442,22 @@ export class PatchContainer { return Object.keys(this.patchLookup) } + get chunkIds() { + return this.availablePatches + .map(patch => patch.chunkIds) + .flat() + } + + get availablePatches() { + return Object.values(this.patchLookup).filter(val => val) as BlocksPatch[] + } + + get missingPatchKeys() { + return Object.keys(this.patchLookup).filter( + key => !this.patchLookup[key], + ) as PatchKey[] + } + // autoFill(fillingVal=0){ // this.patchKeys.forEach(key=>this.patchLookup[key] = new BlocksPatch(key)) // this.availablePatches.forEach(patch=>patch.iterOverBlocks) @@ -460,26 +474,6 @@ export class PatchContainer { }) } - mergeBlocks(blocksContainer: BlocksContainer) { - // // for each patch override with blocks from blocks container - // this.availablePatches.forEach(patch => { - // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) - // for (const target_block of blocksIter) { - // const source_block = blocksContainer.getBlock(target_block.pos, false) - // if (source_block && source_block.pos.y > 0 && target_block.index) { - // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE - // block_type = - // source_block.type === BlockType.TREE_TRUNK - // ? BlockType.TREE_TRUNK - // : block_type - // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y - // patch.writeBlock(target_block.index, block_level, block_type) - // // console.log(source_block?.pos.y) - // } - // } - // }) - } - compareWith(otherContainer: PatchContainer) { const patchKeysDiff: Record = {} // added keys e.g. keys in current container but not found in other @@ -493,6 +487,30 @@ export class PatchContainer { return patchKeysDiff } + toChunks() { + const exportedChunks = this.availablePatches + .map(patch => patch.toChunks()) + .flat() + return exportedChunks + } + + findPatch(blockPos: Vector3) { + // const point = new Vector3( + // inputPoint.x, + // 0, + // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, + // ) + + const res = this.availablePatches.find(patch => + patch.containsBlock(blockPos), + ) + return res + } + + getBlock(blockPos: Vector3) { + return this.findPatch(blockPos)?.getBlock(blockPos, false) + } + getMergedRows(zRowIndex: number) { const sortedPatchesRows = this.availablePatches .filter(patch => zRowIndex >= patch.bbox.min.z && zRowIndex <= patch.bbox.min.z) @@ -538,26 +556,24 @@ export class PatchContainer { static fromMergedContainer() { } - - - toChunks() { - const exportedChunks = this.availablePatches - .map(patch => patch.toChunks()) - .flat() - return exportedChunks - } - - findPatch(blockPos: Vector3) { - // const point = new Vector3( - // inputPoint.x, - // 0, - // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, - // ) - - const res = this.availablePatches.find(patch => - patch.containsBlock(blockPos), - ) - return res + mergeBlocks(blocksContainer: BlocksContainer) { + // // for each patch override with blocks from blocks container + // this.availablePatches.forEach(patch => { + // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + // for (const target_block of blocksIter) { + // const source_block = blocksContainer.getBlock(target_block.pos, false) + // if (source_block && source_block.pos.y > 0 && target_block.index) { + // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE + // block_type = + // source_block.type === BlockType.TREE_TRUNK + // ? BlockType.TREE_TRUNK + // : block_type + // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y + // patch.writeBlock(target_block.index, block_level, block_type) + // // console.log(source_block?.pos.y) + // } + // } + // }) } } diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 1da5d2a..6f0ec56 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -82,18 +82,17 @@ export class ChunkFactory { ) { let written_blocks_count = 0 for (const block of blockIterator) { - const blockLocalPos = block.pos const blockData = block.data const blockType = block.data.type - blockLocalPos.x += 1 - // blockLocalPos.y = patch.bbox.max.y - blockLocalPos.z += 1 + block.localPos.x += 1 + // block.localPos.y = patch.bbox.max.y + block.localPos.z += 1 blockData.type = - highlightPatchBorders(blockLocalPos, blockType) || blockType + highlightPatchBorders(block.localPos, blockType) || blockType written_blocks_count += this.writeChunkBlocks( chunkDataContainer, chunkBox, - blockLocalPos, + block.localPos, blockData, block.buffer ) From ea41cd3275f288cd2e1f4adc72e362b4a8c239c9 Mon Sep 17 00:00:00 2001 From: etienne Date: Mon, 26 Aug 2024 13:43:20 +0000 Subject: [PATCH 15/45] feat: board entities triming --- src/data/BoardContainer.ts | 113 +++++++++++++++++++------------------ src/data/DataContainers.ts | 33 +++++------ src/procgen/EntitiesMap.ts | 5 +- src/tools/ChunkFactory.ts | 8 +-- 4 files changed, 83 insertions(+), 76 deletions(-) diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index f56455a..303aad8 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,94 +1,99 @@ -import { Vector3 } from 'three' +import { Box3, Vector3 } from 'three' +import { Block } from '../common/types' import { asVect2 } from '../common/utils' +import { WorldCacheContainer } from '../index' -import { BlockMode, EntityChunk, PatchContainer } from './DataContainers' +import { BlockMode, PatchContainer } from './DataContainers' export class BoardContainer extends PatchContainer { boardCenter boardRadius - innerEntities: EntityChunk[] = [] - outerEntities: EntityChunk[] = [] - constructor(center: Vector3, radius: number) { + boardMaxHeightDiff + + constructor(center: Vector3, radius: number, maxHeightDiff: number) { super() this.boardRadius = radius this.boardCenter = center.clone().floor() + this.boardMaxHeightDiff = maxHeightDiff const board_dims = new Vector3(radius, 0, radius).multiplyScalar(2) this.bbox.setFromCenterAndSize(this.boardCenter, board_dims) this.initFromBoxAndMask(this.bbox) } - getMinMax() { - const { boardCenter, boardRadius } = this - let ymin = this.bbox.max.y - let ymax = this.bbox.min.y - this.availablePatches.forEach(patch => { - const blocks = patch.iterOverBlocks(this.bbox) - for (const block of blocks) { - // discard blocks not included in board shape - const dist = asVect2(block.pos).distanceTo(asVect2(boardCenter)) - if (dist <= boardRadius) { - const block_level = block.pos.y - ymin = Math.min(block_level, ymin) - ymax = Math.max(block_level, ymax) - } - } - }) - return { ymin, ymax } + isInsideBoard(block: Block) { + // const block = input instanceof Vector3 ? this.getBlock(input) : input + let res = false + if (block) { + const heightDiff = Math.abs(block.pos.y - this.boardCenter.y) + const dist = asVect2(block.pos).distanceTo(asVect2(this.boardCenter)) + res = dist <= this.boardRadius && heightDiff <= this.boardMaxHeightDiff + } + return res } shapeBoard() { - const maxHeightDiff = 5 - const { boardCenter, boardRadius } = this // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) this.availablePatches.forEach(patch => { const blocks = patch.iterOverBlocks(this.bbox) for (const block of blocks) { // discard blocs not included in board shape - const dist = asVect2(block.pos).distanceTo(asVect2(boardCenter)) - const blockData = block.data - blockData.level = boardCenter.y - blockData.mode = BlockMode.BOARD_CONTAINER - const heightDiff = Math.abs(block.pos.y - boardCenter.y) - if (dist <= boardRadius && heightDiff <= maxHeightDiff && block.index !== undefined) { + if (this.isInsideBoard(block) && block.index !== undefined) { + const blockData = block.data + blockData.level = this.boardCenter.y + blockData.mode = BlockMode.BOARD_CONTAINER patch.writeBlockData(block.index, blockData) } } }) } - sortEntities() { + trimEntities() { this.availablePatches.forEach(patch => { patch.entitiesChunks.forEach(entity => { - const localCenter = entity.bbox.getCenter(new Vector3()) - const entityCenter = patch.toGlobalPos(localCenter) - const isWithinBoard = this.bbox.containsPoint(entityCenter) - isWithinBoard ? this.innerEntities.push(entity) : this.outerEntities.push(entity) + const entityMin = patch.toGlobalPos(entity.bbox.min) + const entityMax = patch.toGlobalPos(entity.bbox.max) + const entityBox = new Box3(entityMin, entityMax) + const entityCenter = entityBox.getCenter(new Vector3()) + // console.log(entityCenter) + const entityCenterBlock = this.getBlock(entityCenter) + entityCenter.y = entityMin.y + const isEntityOverlappingBoard = () => { + const entityBlocks = patch.iterEntityBlocks(entity) + for (const block of entityBlocks) { + if (this.isInsideBoard(block)) { + return true + } + } + return false + } + + if (entityCenterBlock && this.isInsideBoard(entityCenterBlock)) { + // trim entities belonging to board + const diff = entityCenter.clone().sub(this.boardCenter) + entity.bbox.max.y = entity.bbox.min.y - diff.y + 2 + } else if (isEntityOverlappingBoard()) { + // discard outside entities having an overlap with the board + entity.bbox.makeEmpty() + } + // else render outside entities with no overlap as usual }) }) } - trimEntities(){ - this.sortEntities() + restoreOriginalPatches() { + const original_patches_container = new PatchContainer() + original_patches_container.initFromBoxAndMask(this.bbox) + original_patches_container.populateFromExisting( + WorldCacheContainer.instance.availablePatches, + true, + ) + return original_patches_container } - // mergeBoardBlocks(blocksContainer: BlocksContainer) { - // // for each patch override with blocks from blocks container - // this.availablePatches.forEach(patch => { - // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) - // for (const target_block of blocksIter) { - // const source_block = blocksContainer.getBlock(target_block.pos, false) - // if (source_block && source_block.pos.y > 0 && target_block.index) { - // let block_type = source_block.type ? BlockType.MUD : BlockType.NONE - // block_type = source_block.type === BlockType.TREE_TRUNK ? BlockType.TREE_TRUNK : block_type - // const block_level = blocksContainer.bbox.min.y//source_block?.pos.y - // patch.writeBlockAtIndex(target_block.index, block_level, block_type) - // // console.log(source_block?.pos.y) - // } - // } - // }) - // } + smoothEdges() { + + } - smoothEdges() { } } diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index a7dbdd8..8fcb8ba 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -34,7 +34,7 @@ export type EntityChunk = { export type PatchStub = { key: string bbox: Box3 - groundBlocks: Uint32Array + rawDataContainer: Uint32Array entitiesChunks: EntityChunk[] } @@ -57,7 +57,7 @@ export class BlocksContainer { dimensions = new Vector3() margin = 0 - groundBlocks: Uint32Array + rawDataContainer: Uint32Array entitiesChunks: EntityChunk[] = [] constructor(bbox: Box3, margin = 1) { @@ -65,16 +65,16 @@ export class BlocksContainer { this.bbox.getSize(this.dimensions) this.margin = margin const { extendedDims } = this - this.groundBlocks = new Uint32Array(extendedDims.x * extendedDims.z) + this.rawDataContainer = new Uint32Array(extendedDims.x * extendedDims.z) } duplicate() { const copy = new BlocksContainer(this.bbox) - this.groundBlocks.forEach( - (v, i) => (copy.groundBlocks[i] = v), - ) - copy.entitiesChunks = this.entitiesChunks - return copy + this.rawDataContainer.forEach( + (v, i) => (copy.rawDataContainer[i] = v), + ) + copy.entitiesChunks = this.entitiesChunks + return copy } decodeBlockData(rawData: number): BlockData { @@ -98,7 +98,7 @@ export class BlocksContainer { } readBlockData(blockIndex: number): BlockData { - const blockRawData = this.groundBlocks[blockIndex] + const blockRawData = this.rawDataContainer[blockIndex] const blockData = this.decodeBlockData(blockRawData) return blockData } @@ -107,7 +107,7 @@ export class BlocksContainer { blockIndex: number, blockData: BlockData ) { - this.groundBlocks[blockIndex] = this.encodeBlockData(blockData) + this.rawDataContainer[blockIndex] = this.encodeBlockData(blockData) } get extendedBox() { @@ -178,6 +178,7 @@ export class BlocksContainer { pos.y = data.level block = { pos, + localPos, data } } @@ -199,7 +200,7 @@ export class BlocksContainer { getBlocksRow(zRowIndex: number) { const rowStart = zRowIndex * this.dimensions.z const rowEnd = rowStart + this.dimensions.x - const rowRawData = this.groundBlocks.slice(rowStart, rowEnd) + const rowRawData = this.rawDataContainer.slice(rowStart, rowEnd) return rowRawData } @@ -325,9 +326,9 @@ export class BlocksContainer { } static fromStub(stub: any) { - const { groundBlocks, entitiesChunks } = stub + const { rawDataContainer, entitiesChunks } = stub const blocksContainer = new BlocksContainer(parseThreeStub(stub.bbox)) - blocksContainer.groundBlocks = groundBlocks + blocksContainer.rawDataContainer = rawDataContainer blocksContainer.entitiesChunks = entitiesChunks // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => // patch.entitiesChunks.push(entityChunk), @@ -351,7 +352,7 @@ export class BlocksPatch extends BlocksContainer { override duplicate() { const copy = new BlocksPatch(this.key) - this.groundBlocks.forEach((rawVal, i) => copy.groundBlocks[i] = rawVal) + this.rawDataContainer.forEach((rawVal, i) => copy.rawDataContainer[i] = rawVal) copy.entitiesChunks = this.entitiesChunks.map(entity => { const entityCopy: EntityChunk = { bbox: entity.bbox.clone(), @@ -363,11 +364,11 @@ export class BlocksPatch extends BlocksContainer { } static override fromStub(patchStub: any) { - const { groundBlocks, entitiesChunks } = patchStub + const { rawDataContainer, entitiesChunks } = patchStub const bbox = parseThreeStub(patchStub.bbox) const patchKey = patchStub.key || computePatchKey(bbox) const patch = new BlocksPatch(patchKey) - patch.groundBlocks = groundBlocks + patch.rawDataContainer = rawDataContainer patch.entitiesChunks = entitiesChunks.map((stub: EntityChunk) => { const entityChunk: EntityChunk = { bbox: parseThreeStub(stub.bbox), diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index 3683148..e8008be 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -125,8 +125,9 @@ export class EntitiesMap { } }) } - - return sum > 0 ? treeBuffer : [] + const res = sum > 0 ? treeBuffer : [] + entity.bbox.max.y = entity.bbox.min.y + 20//res.length + return res } /** diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 6f0ec56..fffa55f 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,11 +1,11 @@ import { Box3, MathUtils, Vector3 } from 'three' -import { Block, ChunkId, PatchId, WorldChunk } from '../common/types' -import { asVect3, getBboxFromChunkId, serializeChunkId } from '../common/utils' +import { Block, PatchId } from '../common/types' +import { asVect3 } from '../common/utils' import { BlockData } from '../data/DataContainers' -import { BlockMode, BlocksContainer, BlocksPatch, BlockType, WorldConfig } from '../index' +import { BlockMode, BlockType } from '../index' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND // disabled if NONE +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { From 4a0c085fc8914cba7ceb68171f4d6d8e1c8007db Mon Sep 17 00:00:00 2001 From: etienne Date: Mon, 26 Aug 2024 14:18:32 +0000 Subject: [PATCH 16/45] fix: style/type, code cleanup --- src/api/world-compute.ts | 20 ++- src/common/types.ts | 2 +- src/data/BoardContainer.ts | 7 +- src/data/DataContainers.ts | 139 ++++------------ src/index.ts | 3 +- src/procgen/EntitiesMap.ts | 2 +- src/tools/ChunkFactory.ts | 23 ++- src/utils/plateau_legacy.ts | 307 ------------------------------------ 8 files changed, 66 insertions(+), 437 deletions(-) delete mode 100644 src/utils/plateau_legacy.ts diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index c30d16c..dfa2ed9 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -29,19 +29,19 @@ export const computeBlocksBatch = ( ) => { const blocksBatch = blockPosBatch.map(({ x, z }) => { const blockPos = new Vector3(x, 0, z) - const blockStub = computeGroundBlock(blockPos) + const blockData = computeGroundBlock(blockPos) if (params.includeEntitiesBlocks) { const blocksBuffer = computeBlocksBuffer(blockPos) const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) if (lastBlockIndex >= 0) { - blockStub.level += lastBlockIndex - blockStub.type = blocksBuffer[lastBlockIndex] as BlockType + blockData.level += lastBlockIndex + blockData.type = blocksBuffer[lastBlockIndex] as BlockType } } - blockPos.y = blockStub.level + blockPos.y = blockData.level const block: Block = { pos: blockPos, - type: blockStub.type, + data: blockData, } return block }) @@ -99,16 +99,20 @@ const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { } const blocksIter = patch.iterOverBlocks(entity.bbox, true) for (const block of blocksIter) { - const blocksBuffer = EntitiesMap.fillBlockBuffer(block.localPos, entity, []) + const blocksBuffer = EntitiesMap.fillBlockBuffer( + block.localPos as Vector3, + entity, + [], + ) patch.bbox.max.y = Math.max( patch.bbox.max.y, - block.localPos.y + blocksBuffer.length, + (block.localPos as Vector3).y + blocksBuffer.length, ) const serialized = blocksBuffer .reduce((str, val) => str + ',' + val, '') .slice(1) entityChunk.data.push(serialized) - entityChunk.bbox.expandByPoint(block.localPos) + entityChunk.bbox.expandByPoint(block.localPos as Vector3) } entityChunk.bbox = entity.bbox return entityChunk diff --git a/src/common/types.ts b/src/common/types.ts index b3168e8..7fe7894 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,6 +1,6 @@ import { Vector2, Vector3 } from 'three' -import { BlockData } from '../data/DataContainers' +import { BlockData } from '../data/DataContainers' import { BiomeType, BlockType } from '../procgen/Biome' import { LinkedList } from './misc' diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index 303aad8..6892fc3 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,6 +1,6 @@ import { Box3, Vector3 } from 'three' -import { Block } from '../common/types' +import { Block } from '../common/types' import { asVect2 } from '../common/utils' import { WorldCacheContainer } from '../index' @@ -92,8 +92,5 @@ export class BoardContainer extends PatchContainer { return original_patches_container } - smoothEdges() { - - } - + smoothEdges() {} } diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 8fcb8ba..d4e5d78 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -15,17 +15,17 @@ import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' +export enum BlockMode { + DEFAULT, + BOARD_CONTAINER, +} + export type BlockData = { level: number type: BlockType mode?: BlockMode } -export enum BlockMode { - DEFAULT, - BOARD_CONTAINER -} - export type EntityChunk = { bbox: Box3 data: string[] @@ -41,9 +41,9 @@ export type PatchStub = { // bits allocated per block data type // total bits required to store a block: 9+10+3 = 22 bits const BlockDataBitAllocation = { - level: 9, // support level values ranging from 0 to 512 - type: 10, // support up to 1024 different block types - mode: 3, // support for 8 different block mode + level: 9, // support level values ranging from 0 to 512 + type: 10, // support up to 1024 different block types + mode: 3, // support for 8 different block mode } export type BlockIteratorRes = IteratorResult @@ -70,20 +70,21 @@ export class BlocksContainer { duplicate() { const copy = new BlocksContainer(this.bbox) - this.rawDataContainer.forEach( - (v, i) => (copy.rawDataContainer[i] = v), - ) - copy.entitiesChunks = this.entitiesChunks - return copy + this.rawDataContainer.forEach((v, i) => (copy.rawDataContainer[i] = v)) + copy.entitiesChunks = this.entitiesChunks + return copy } decodeBlockData(rawData: number): BlockData { const shift = BlockDataBitAllocation - const level = (rawData >> (shift.type + shift.mode)) & ((1 << shift.level) - 1); // Extract 9 bits for level - const type = (rawData >> shift.mode) & ((1 << shift.type) - 1); // Extract 10 bits for type - const mode = rawData & ((1 << shift.mode) - 1); // Extract 3 bits for mode + const level = + (rawData >> (shift.type + shift.mode)) & ((1 << shift.level) - 1) // Extract 9 bits for level + const type = (rawData >> shift.mode) & ((1 << shift.type) - 1) // Extract 10 bits for type + const mode = rawData & ((1 << shift.mode) - 1) // Extract 3 bits for mode const blockData: BlockData = { - level, type, mode + level, + type, + mode, } return blockData } @@ -92,21 +93,18 @@ export class BlocksContainer { const { level, type, mode } = blockData const shift = BlockDataBitAllocation let blockRawVal = level - blockRawVal = blockRawVal << shift.type | type - blockRawVal = blockRawVal << shift.mode | (mode || BlockMode.DEFAULT) + blockRawVal = (blockRawVal << shift.type) | type + blockRawVal = (blockRawVal << shift.mode) | (mode || BlockMode.DEFAULT) return blockRawVal } readBlockData(blockIndex: number): BlockData { const blockRawData = this.rawDataContainer[blockIndex] - const blockData = this.decodeBlockData(blockRawData) + const blockData = this.decodeBlockData(blockRawData as number) return blockData } - writeBlockData( - blockIndex: number, - blockData: BlockData - ) { + writeBlockData(blockIndex: number, blockData: BlockData) { this.rawDataContainer[blockIndex] = this.encodeBlockData(blockData) } @@ -179,7 +177,7 @@ export class BlocksContainer { block = { pos, localPos, - data + data, } } return block @@ -189,7 +187,7 @@ export class BlocksContainer { const blockIndex = localPos.x * this.dimensions.x + localPos.z const block = { level: localPos.y, - type: blockType + type: blockType, } this.writeBlockData(blockIndex, block) // const levelMax = blockLevel + blockData.over.length @@ -204,9 +202,9 @@ export class BlocksContainer { return rowRawData } - getBlocksCol(xColIndex: number) { + // getBlocksCol(xColIndex: number) { - } + // } *iterOverBlocks(customBox?: Box3, useLocalPos = false, skipMargin = true) { const bbox = customBox @@ -248,16 +246,13 @@ export class BlocksContainer { *iterEntityBlocks(entity: EntityChunk) { // find overlapping blocks between entity and container - const blocks_iter = this.iterOverBlocks( - entity.bbox, - true, - ) + const blocks_iter = this.iterOverBlocks(entity.bbox, true) let chunk_index = 0 // iter over entity blocks for (const block of blocks_iter) { const bufferStr = entity.data[chunk_index++] const buffer = bufferStr?.split(',').map(char => parseInt(char)) - const maxHeightDiff = entity.bbox.max.y - block.localPos.y + const maxHeightDiff = entity.bbox.max.y - (block.localPos as Vector3).y const entityBlockData = block entityBlockData.buffer = buffer?.slice(0, maxHeightDiff) || [] yield entityBlockData @@ -352,11 +347,13 @@ export class BlocksPatch extends BlocksContainer { override duplicate() { const copy = new BlocksPatch(this.key) - this.rawDataContainer.forEach((rawVal, i) => copy.rawDataContainer[i] = rawVal) + this.rawDataContainer.forEach( + (rawVal, i) => (copy.rawDataContainer[i] = rawVal), + ) copy.entitiesChunks = this.entitiesChunks.map(entity => { const entityCopy: EntityChunk = { bbox: entity.bbox.clone(), - data: entity.data.slice() + data: entity.data.slice(), } return entityCopy }) @@ -372,7 +369,7 @@ export class BlocksPatch extends BlocksContainer { patch.entitiesChunks = entitiesChunks.map((stub: EntityChunk) => { const entityChunk: EntityChunk = { bbox: parseThreeStub(stub.bbox), - data: stub.data + data: stub.data, } return entityChunk }) @@ -444,9 +441,7 @@ export class PatchContainer { } get chunkIds() { - return this.availablePatches - .map(patch => patch.chunkIds) - .flat() + return this.availablePatches.map(patch => patch.chunkIds).flat() } get availablePatches() { @@ -511,70 +506,4 @@ export class PatchContainer { getBlock(blockPos: Vector3) { return this.findPatch(blockPos)?.getBlock(blockPos, false) } - - getMergedRows(zRowIndex: number) { - const sortedPatchesRows = this.availablePatches - .filter(patch => zRowIndex >= patch.bbox.min.z && zRowIndex <= patch.bbox.min.z) - .sort((p1, p2) => p1.bbox.min.x - p2.bbox.min.x) - .map(patch => patch.getBlocksRow(zRowIndex)) - const mergedRows = sortedPatchesRows.reduce((arr1, arr2) => { - const mergedArray = new Uint32Array(arr1.length + arr2.length) - mergedArray.set(arr1) - mergedArray.set(arr2, arr1.length) - return mergedArray - }) - return mergedRows - } - - iterMergedRows() { - const { min, max } = this.patchRange - for (let zPatchIndex = min.z; zPatchIndex <= max.z; zPatchIndex++) { - for (let zRowIndex = min.z; zRowIndex < max.z; zRowIndex++) { - - } - } - } - - getMergedCols(xColIndex: number) { - - } - - mergedLinesIteration() { - const { min, max } = this.bbox - for (let x = min.x; x < max.x; x++) { - for (let z = min.z; z < max.z; z++) { - - } - } - } - - toMergedContainer() { - const mergedBox = this.availablePatches.map(patch => patch.bbox) - .reduce((merge, bbox) => merge.union(bbox), new Box3()) - // const mergedContainer = - } - - static fromMergedContainer() { - - } - mergeBlocks(blocksContainer: BlocksContainer) { - // // for each patch override with blocks from blocks container - // this.availablePatches.forEach(patch => { - // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) - // for (const target_block of blocksIter) { - // const source_block = blocksContainer.getBlock(target_block.pos, false) - // if (source_block && source_block.pos.y > 0 && target_block.index) { - // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE - // block_type = - // source_block.type === BlockType.TREE_TRUNK - // ? BlockType.TREE_TRUNK - // : block_type - // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y - // patch.writeBlock(target_block.index, block_level, block_type) - // // console.log(source_block?.pos.y) - // } - // } - // }) - } } - diff --git a/src/index.ts b/src/index.ts index 460f1f0..bd13f40 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ export { BlocksContainer, BlocksPatch, PatchContainer, - BlockMode + BlockMode, } from './data/DataContainers' export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' @@ -17,7 +17,6 @@ export { WorldConfig } from './config/WorldConfig' export { WorldComputeApi } from './api/WorldComputeApi' export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' -export * as PlateauLegacy from './utils/plateau_legacy' // export type { MappingConf, MappingData, MappingRanges } from "./common/types" // export { DevHacks } from './tools/DevHacks' diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index e8008be..85515b5 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -126,7 +126,7 @@ export class EntitiesMap { }) } const res = sum > 0 ? treeBuffer : [] - entity.bbox.max.y = entity.bbox.min.y + 20//res.length + entity.bbox.max.y = entity.bbox.min.y + 20 // res.length return res } diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index fffa55f..262c0b5 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -17,7 +17,10 @@ const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { export class ChunkFactory { // eslint-disable-next-line no-use-before-define static defaultInstance: ChunkFactory - voxelDataEncoder = (blockType: BlockType, blockMode?: BlockMode) => blockType || BlockType.NONE + // eslint-disable-next-line @typescript-eslint/no-unused-vars + voxelDataEncoder = (blockType: BlockType, _blockMode?: BlockMode) => + blockType || BlockType.NONE + chunksRange = { ymin: 0, ymax: 5, @@ -40,7 +43,7 @@ export class ChunkFactory { blockData: BlockData, bufferOver: any[] = [], ) { - const chunk_size = chunkBbox.getSize(new Vector3()).x //Math.round(Math.pow(chunkDataContainer.length, 1 / 3)) + const chunk_size = chunkBbox.getSize(new Vector3()).x // Math.round(Math.pow(chunkDataContainer.length, 1 / 3)) let written_blocks_count = 0 @@ -66,7 +69,10 @@ export class ChunkFactory { chunkDataContainer[blocksIndex] !== undefined && !bufferOver[buff_index] if (!skip) { - chunkDataContainer[blocksIndex] = this.voxelDataEncoder(blockType, blockData.mode) + chunkDataContainer[blocksIndex] = this.voxelDataEncoder( + blockType, + blockData.mode, + ) blockType && written_blocks_count++ } buff_index-- @@ -84,17 +90,18 @@ export class ChunkFactory { for (const block of blockIterator) { const blockData = block.data const blockType = block.data.type - block.localPos.x += 1 + const blockLocalPos = block.localPos as Vector3 + blockLocalPos.x += 1 // block.localPos.y = patch.bbox.max.y - block.localPos.z += 1 + blockLocalPos.z += 1 blockData.type = - highlightPatchBorders(block.localPos, blockType) || blockType + highlightPatchBorders(blockLocalPos, blockType) || blockType written_blocks_count += this.writeChunkBlocks( chunkDataContainer, chunkBox, - block.localPos, + blockLocalPos, blockData, - block.buffer + block.buffer, ) } return written_blocks_count diff --git a/src/utils/plateau_legacy.ts b/src/utils/plateau_legacy.ts deleted file mode 100644 index 282aa48..0000000 --- a/src/utils/plateau_legacy.ts +++ /dev/null @@ -1,307 +0,0 @@ -// import * as THREE from '../../../three-usage'; -// import { voxelmapDataPacking, type IVoxelMap } from '../i-voxelmap'; - -import { Box3, Vector3, Vector3Like } from 'three' - -import { ChunkTools } from '../index' -import { BlocksContainer } from '../data/DataContainers' - -enum EPlateauSquareType { - FLAT = 0, - HOLE = 1, - OBSTACLE = 2, -} - -type PlateauSquare = { - readonly type: EPlateauSquareType - readonly materialId: number -} - -type ColumnId = { readonly x: number; readonly z: number } - -type Plateau = { - readonly id: number - readonly size: { readonly x: number; readonly z: number } - readonly squares: ReadonlyArray - readonly origin: Vector3Like -} - -type PlateauSquareExtended = PlateauSquare & { - readonly floorY: number - readonly generation: number -} - -let plateauxCount = 0 - -async function computePlateau(originWorld: Vector3Like): Promise { - originWorld = { - x: Math.floor(originWorld.x), - y: Math.floor(originWorld.y), - z: Math.floor(originWorld.z), - } - - let currentGeneration = 0 - const maxDeltaY = 4 - const plateauHalfSize = 31 - const plateauSize = { x: 2 * plateauHalfSize + 1, z: 2 * plateauHalfSize + 1 } - const plateauSquares: PlateauSquareExtended[] = [] - for (let iZ = 0; iZ < plateauSize.z; iZ++) { - for (let iX = 0; iX < plateauSize.x; iX++) { - plateauSquares.push({ - type: EPlateauSquareType.HOLE, - materialId: 0, - floorY: NaN, - generation: currentGeneration, - }) - } - } - const tryGetIndex = (relativePos: ColumnId) => { - const plateauCoords = { - x: relativePos.x + plateauHalfSize, - z: relativePos.z + plateauHalfSize, - } - if ( - plateauCoords.x < 0 || - plateauCoords.z < 0 || - plateauCoords.x >= plateauSize.x || - plateauCoords.z >= plateauSize.z - ) { - return null - } - return plateauCoords.x + plateauCoords.z * plateauSize.x - } - const getIndex = (relativePos: ColumnId) => { - const index = tryGetIndex(relativePos) - if (index === null) { - throw new Error() - } - return index - } - const setPlateauSquare = ( - relativePos: ColumnId, - square: PlateauSquareExtended, - ) => { - const index = getIndex(relativePos) - plateauSquares[index] = { ...square } - } - const getPlateauSquare = (relativePos: ColumnId) => { - const index = getIndex(relativePos) - return plateauSquares[index]! - } - const tryGetPlateauSquare = (relativePos: ColumnId) => { - const index = tryGetIndex(relativePos) - if (index === null) { - return null - } - return plateauSquares[index] - } - - const dataMargin = plateauHalfSize + 5 - const dataFromWorld = new Vector3().copy(originWorld).subScalar(dataMargin) - const dataToWorld = new Vector3().copy(originWorld).addScalar(dataMargin) - const dataBbox = new Box3(dataFromWorld, dataToWorld) - const containerStub: any = null // await WorldCompute.instance.iterPatchCompute() - const blocksContainer = BlocksContainer.fromStub(containerStub) - const chunk = ChunkTools.makeChunkFromBox(blocksContainer, dataBbox) - const data = chunk // await map.getLocalMapData(dataFromWorld, dataToWorld) - const dataSize = dataToWorld.clone().sub(dataFromWorld) - - const sampleData = (worldPos: Vector3Like) => { - const dataPos = new Vector3().copy(worldPos).sub(dataFromWorld) - if ( - dataPos.x < 0 || - dataPos.y < 0 || - dataPos.z < 0 || - dataPos.x >= dataSize.x || - dataPos.y >= dataSize.y || - dataPos.z >= dataSize.z - ) { - throw new Error() - } - const index = - dataPos.x + dataPos.y * dataSize.x + dataPos.z * dataSize.x * dataSize.y - return data.data?.[index]! - } - - { - const originWorldCoords = { - x: originWorld.x, - y: originWorld.y, - z: originWorld.z, - } - let originSample = sampleData(originWorldCoords) - let deltaY = 0 - while (!originSample && deltaY < maxDeltaY) { - originWorldCoords.y-- - deltaY++ - originSample = sampleData(originWorldCoords) - } - if (!originSample) { - throw new Error() - } - setPlateauSquare( - { x: 0, z: 0 }, - { - type: EPlateauSquareType.FLAT, - materialId: originSample, - generation: currentGeneration, - floorY: originWorldCoords.y - 1, - }, - ) - } - const originY = getPlateauSquare({ x: 0, z: 0 })!.floorY - - const computePlateauSquare = ( - relativePos: ColumnId, - ): PlateauSquareExtended | null => { - const square = getPlateauSquare(relativePos) - if (square.type !== EPlateauSquareType.HOLE) { - // this square has been computed already - return null - } - - // if this square has not been computed yet - const xm = tryGetPlateauSquare({ x: relativePos.x - 1, z: relativePos.z }) - const xp = tryGetPlateauSquare({ x: relativePos.x + 1, z: relativePos.z }) - const zm = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z - 1 }) - const zp = tryGetPlateauSquare({ x: relativePos.x, z: relativePos.z + 1 }) - - const worldPos = { x: 0, y: 0, z: 0 } - worldPos.x = relativePos.x + originWorld.x - worldPos.z = relativePos.z + originWorld.z - - for (const neighbour of [xm, xp, zm, zp]) { - if ( - neighbour?.type === EPlateauSquareType.FLAT && - neighbour.generation === currentGeneration - 1 - ) { - worldPos.y = neighbour.floorY - const generation = currentGeneration - const sampleY = sampleData(worldPos) - - if (sampleY) { - let firstSample: number | null = null - let lastSample = sampleY - for (let deltaY = 1; deltaY < maxDeltaY; deltaY++) { - const sample = sampleData({ - x: worldPos.x, - y: worldPos.y + deltaY, - z: worldPos.z, - }) - if (!sample) { - return { - type: EPlateauSquareType.FLAT, - materialId: lastSample, - floorY: worldPos.y + deltaY - 1, - generation, - } - } else { - firstSample = firstSample ?? sample - lastSample = sample - } - } - - if (!firstSample) { - throw new Error() - } - - return { - type: EPlateauSquareType.OBSTACLE, - materialId: firstSample, - floorY: worldPos.y, - generation, - } - } else { - for (let deltaY = -1; deltaY > -maxDeltaY; deltaY--) { - const sample = sampleData({ - x: worldPos.x, - y: worldPos.y + deltaY, - z: worldPos.z, - }) - if (sample) { - return { - type: EPlateauSquareType.FLAT, - materialId: sample, - floorY: worldPos.y + deltaY, - generation, - } - } - } - - return { - type: EPlateauSquareType.HOLE, - materialId: 0, - floorY: NaN, - generation, - } - } - } - } - - return null - } - - let somethingChanged = false - do { - somethingChanged = false - currentGeneration++ - - const relativePos = { x: 0, z: 0 } - for ( - relativePos.z = -plateauHalfSize; - relativePos.z <= plateauHalfSize; - relativePos.z++ - ) { - for ( - relativePos.x = -plateauHalfSize; - relativePos.x <= plateauHalfSize; - relativePos.x++ - ) { - if ( - Math.sqrt( - relativePos.x * relativePos.x + relativePos.z * relativePos.z, - ) >= - plateauHalfSize - 1 - ) { - continue - } - - const square = computePlateauSquare(relativePos) - if ( - square && - !isNaN(square.floorY) && - Math.abs(square.floorY - originY) < maxDeltaY - ) { - somethingChanged = true - setPlateauSquare(relativePos, square) - } - } - } - } while (somethingChanged) - - const minY = plateauSquares.reduce( - (y: number, square: PlateauSquareExtended) => { - if (!isNaN(square.floorY)) { - return Math.min(y, square.floorY) - } - return y - }, - originY, - ) - const plateauYShift = minY - originY - 1 - - const plateauOrigin = new Vector3( - originWorld.x - plateauHalfSize, - originWorld.y + plateauYShift, - originWorld.z - plateauHalfSize, - ) - - return { - id: plateauxCount++, - size: plateauSize, - squares: plateauSquares, - origin: plateauOrigin, - } -} - -export { computePlateau, EPlateauSquareType, type Plateau, type PlateauSquare } From 94d4ae2a5454c288fc4ceb9f27575706724597e1 Mon Sep 17 00:00:00 2001 From: etienne Date: Mon, 26 Aug 2024 14:43:37 +0000 Subject: [PATCH 17/45] fix: type --- src/api/WorldComputeApi.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeApi.ts index 080aa3a..b8b8c18 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -11,6 +11,12 @@ export enum ComputeApiCall { OvergroundBufferCompute = 'computeOvergroundBuffer', } +export type ComputeApiParams = Partial<{ + rememberMe: boolean // allow for caching value + preCacheRadius: number // pre-caching next requests + includeEntitiesBlocks: boolean // skip or include entities blocks +}> + interface ComputeApiInterface { computeBlocksBatch( blockPosBatch: Vector3[], @@ -44,7 +50,7 @@ export class WorldComputeApi implements ComputeApiInterface { computeBlocksBatch( blockPosBatch: Vector3[], - params: ComputeApiParams = { includeEntitiesBlocks: true }, + params = { includeEntitiesBlocks: true }, ) { return WorldCompute.computeBlocksBatch(blockPosBatch, params) } From 3bfce284c251950ea1c3c493a644aea02fc3567b Mon Sep 17 00:00:00 2001 From: etienne Date: Mon, 26 Aug 2024 17:18:20 +0000 Subject: [PATCH 18/45] fix: board margin blocks --- src/data/BoardContainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index 6892fc3..69ad28e 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -36,7 +36,7 @@ export class BoardContainer extends PatchContainer { // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) this.availablePatches.forEach(patch => { - const blocks = patch.iterOverBlocks(this.bbox) + const blocks = patch.iterOverBlocks(undefined, false, false) for (const block of blocks) { // discard blocs not included in board shape if (this.isInsideBoard(block) && block.index !== undefined) { From 3a4d6311f6c3f70b86b3ae99720748ad08d799b4 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 27 Aug 2024 15:34:30 +0000 Subject: [PATCH 19/45] refactor: entities + defaults to using global pos --- src/api/world-compute.ts | 22 ++--- src/data/BoardContainer.ts | 68 ++++++++------ src/data/DataContainers.ts | 179 +++++++++++++++++++++++++++---------- src/procgen/EntitiesMap.ts | 80 +++++++++-------- src/tools/ChunkFactory.ts | 9 +- 5 files changed, 228 insertions(+), 130 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index dfa2ed9..61a3586 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -97,10 +97,10 @@ const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { bbox: new Box3(), data: [], } - const blocksIter = patch.iterOverBlocks(entity.bbox, true) + const blocksIter = patch.iterOverBlocks(entity.bbox) for (const block of blocksIter) { const blocksBuffer = EntitiesMap.fillBlockBuffer( - block.localPos as Vector3, + block.pos, entity, [], ) @@ -112,7 +112,7 @@ const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { .reduce((str, val) => str + ',' + val, '') .slice(1) entityChunk.data.push(serialized) - entityChunk.bbox.expandByPoint(block.localPos as Vector3) + entityChunk.bbox.expandByPoint(block.pos as Vector3) } entityChunk.bbox = entity.bbox return entityChunk @@ -133,13 +133,8 @@ const genEntitiesBlocks = (blocksContainer: BlocksContainer) => { // 0, // max.z % patch.dimensions.z + max.z >= 0 ? 0 : patch.dimensions.z) if (entityType) { - const dims = entity.bbox.getSize(new Vector3()) - dims.y = 10 - const localBmin = entity.bbox.min.clone().sub(blocksContainer.bbox.min) - localBmin.y = Heightmap.instance.getGroundLevel(entityPos) - const localBmax = localBmin.clone().add(dims) - const localBbox = new Box3(localBmin, localBmax) - entity.bbox = localBbox + entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos) + entity.bbox.max.y = entity.bbox.min.y + 10 entity.type = entityType const entityChunk = buildEntityChunk(blocksContainer, entity) blocksContainer.entitiesChunks.push(entityChunk) @@ -157,15 +152,10 @@ const genGroundBlocks = (blocksContainer: BlocksContainer) => { // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const blocksPatchIter = blocksContainer.iterOverBlocks( - undefined, - false, - false, - ) + const blocksPatchIter = blocksContainer.iterOverBlocks(undefined, false,) min.y = 512 max.y = 0 let blockIndex = 0 - for (const block of blocksPatchIter) { // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) const blockData = computeGroundBlock(block.pos) diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index 69ad28e..683c4b8 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -4,7 +4,7 @@ import { Block } from '../common/types' import { asVect2 } from '../common/utils' import { WorldCacheContainer } from '../index' -import { BlockMode, PatchContainer } from './DataContainers' +import { BlockData, BlockMode, PatchContainer } from './DataContainers' export class BoardContainer extends PatchContainer { boardCenter @@ -21,55 +21,71 @@ export class BoardContainer extends PatchContainer { this.initFromBoxAndMask(this.bbox) } - isInsideBoard(block: Block) { - // const block = input instanceof Vector3 ? this.getBlock(input) : input - let res = false - if (block) { - const heightDiff = Math.abs(block.pos.y - this.boardCenter.y) - const dist = asVect2(block.pos).distanceTo(asVect2(this.boardCenter)) - res = dist <= this.boardRadius && heightDiff <= this.boardMaxHeightDiff + isInsideBoardFilter(blockPos: Vector3) { + let isInsideBoard = false + if (blockPos) { + const heightDiff = Math.abs(blockPos.y - this.boardCenter.y) + const dist = asVect2(blockPos).distanceTo(asVect2(this.boardCenter)) + isInsideBoard = dist <= this.boardRadius && heightDiff <= this.boardMaxHeightDiff } - return res + return isInsideBoard + } + + overrideBlock(block: Block) { + const blockData = block.data + blockData.level = this.boardCenter.y + blockData.mode = BlockMode.BOARD_CONTAINER + return block } shapeBoard() { // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) - this.availablePatches.forEach(patch => { - const blocks = patch.iterOverBlocks(undefined, false, false) + // reset bbox to refine bounds + this.bbox.min = this.boardCenter.clone() + this.bbox.max = this.boardCenter.clone() + + for (const patch of this.availablePatches) { + const blocks = patch.iterOverBlocks(undefined, false) + // const blocks = this.iterPatchesBlocks() for (const block of blocks) { // discard blocs not included in board shape - if (this.isInsideBoard(block) && block.index !== undefined) { - const blockData = block.data - blockData.level = this.boardCenter.y - blockData.mode = BlockMode.BOARD_CONTAINER - patch.writeBlockData(block.index, blockData) + if (this.isInsideBoardFilter(block.pos)) { + const boardBlock = this.overrideBlock(block) + patch.writeBlockData(boardBlock.index, boardBlock.data) + this.bbox.expandByPoint(boardBlock.pos) + // yield boardBlock } } - }) + } + } + + getBoardEntities() { + const boardEntities = this.getAllPatchesEntities() + .filter(ent => { + const entityCenter = ent.bbox.getCenter(new Vector3()) + return this.isInsideBoardFilter(entityCenter) + }) + return boardEntities } - trimEntities() { + trimTrees() { this.availablePatches.forEach(patch => { patch.entitiesChunks.forEach(entity => { - const entityMin = patch.toGlobalPos(entity.bbox.min) - const entityMax = patch.toGlobalPos(entity.bbox.max) - const entityBox = new Box3(entityMin, entityMax) - const entityCenter = entityBox.getCenter(new Vector3()) - // console.log(entityCenter) + const entityCenter = entity.bbox.getCenter(new Vector3()) const entityCenterBlock = this.getBlock(entityCenter) - entityCenter.y = entityMin.y + entityCenter.y = entity.bbox.min.y const isEntityOverlappingBoard = () => { const entityBlocks = patch.iterEntityBlocks(entity) for (const block of entityBlocks) { - if (this.isInsideBoard(block)) { + if (this.isInsideBoardFilter(block.pos)) { return true } } return false } - if (entityCenterBlock && this.isInsideBoard(entityCenterBlock)) { + if (entityCenterBlock && this.isInsideBoardFilter(entityCenterBlock.pos)) { // trim entities belonging to board const diff = entityCenter.clone().sub(this.boardCenter) entity.bbox.max.y = entity.bbox.min.y - diff.y + 2 diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index d4e5d78..20f1c27 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -116,33 +116,30 @@ export class BlocksContainer { return this.extendedBox.getSize(new Vector3()) } - get localExtendedBox() { - const bbox = new Box3( + get localBox() { + const localBox = new Box3( new Vector3(0), this.dimensions.clone(), - ).expandByScalar(this.margin) - return bbox + ) + return localBox } - adaptCustomBox(bbox: Box3, useLocalPos = false) { - const { patchSize } = WorldConfig - const bmin = new Vector3( - Math.max(Math.floor(bbox.min.x), useLocalPos ? 0 : this.bbox.min.x), + get localExtendedBox() { + return this.localBox.expandByScalar(this.margin) + } + adjustRangeBox(rangeBox: Box3, local = false) { + const { min, max } = local ? this.localBox : this.bbox + const rangeMin = new Vector3( + Math.max(Math.floor(rangeBox.min.x), min.x), 0, - Math.max(Math.floor(bbox.min.z), useLocalPos ? 0 : this.bbox.min.z), + Math.max(Math.floor(rangeBox.min.z), min.z), ) - const bmax = new Vector3( - Math.min( - Math.floor(bbox.max.x), - useLocalPos ? patchSize : this.bbox.max.x, - ), + const rangeMax = new Vector3( + Math.min(Math.floor(rangeBox.max.x), max.x), 0, - Math.min( - Math.floor(bbox.max.z), - useLocalPos ? patchSize : this.bbox.max.z, - ), + Math.min(Math.floor(rangeBox.max.z), max.z), ) - return new Box3(bmin, bmax) + return local ? new Box3(rangeMin, rangeMax) : new Box3(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) } getBlockIndex(localPos: Vector3) { @@ -154,11 +151,15 @@ export class BlocksContainer { } toLocalPos(pos: Vector3) { - return pos.clone().sub(this.bbox.min) + const origin = this.bbox.min.clone() + origin.y = 0 + return pos.clone().sub(origin) } toGlobalPos(pos: Vector3) { - return this.bbox.min.clone().add(pos) + const origin = this.bbox.min.clone() + origin.y = 0 + return origin.add(pos) } getBlock(pos: Vector3, isLocalPos = true) { @@ -206,34 +207,36 @@ export class BlocksContainer { // } - *iterOverBlocks(customBox?: Box3, useLocalPos = false, skipMargin = true) { - const bbox = customBox - ? this.adaptCustomBox(customBox, useLocalPos) - : useLocalPos - ? this.localExtendedBox - : this.extendedBox + /** + * + * @param rangeBox iteration range as global coords + * @param skipMargin + */ + *iterOverBlocks(rangeBox?: Box3, skipMargin = true) { + // convert to local coords to speed up iteration + const localBbox = rangeBox + ? this.adjustRangeBox(rangeBox) + : this.localExtendedBox const isMarginBlock = ({ x, z }: { x: number; z: number }) => - !customBox && + !rangeBox && this.margin > 0 && - (x === bbox.min.x || - x === bbox.max.x - 1 || - z === bbox.min.z || - z === bbox.max.z - 1) + (x === localBbox.min.x || + x === localBbox.max.x - 1 || + z === localBbox.min.z || + z === localBbox.max.z - 1) let index = 0 - for (let { x } = bbox.min; x < bbox.max.x; x++) { - for (let { z } = bbox.min; z < bbox.max.z; z++) { - const pos = new Vector3(x, 0, z) - if (!skipMargin || !isMarginBlock(pos)) { - const localPos = useLocalPos ? pos : this.toLocalPos(pos) - index = customBox ? this.getBlockIndex(localPos) : index + for (let { x } = localBbox.min; x < localBbox.max.x; x++) { + for (let { z } = localBbox.min; z < localBbox.max.z; z++) { + const localPos = new Vector3(x, 0, z) + if (!skipMargin || !isMarginBlock(localPos)) { + index = rangeBox ? this.getBlockIndex(localPos) : index const blockData = this.readBlockData(index) || BlockType.NONE - pos.y = blockData.level localPos.y = blockData.level const block: Block = { index, - pos: useLocalPos ? this.toGlobalPos(pos) : pos, + pos: this.toGlobalPos(localPos), localPos, data: blockData, } @@ -246,14 +249,14 @@ export class BlocksContainer { *iterEntityBlocks(entity: EntityChunk) { // find overlapping blocks between entity and container - const blocks_iter = this.iterOverBlocks(entity.bbox, true) + const entityBlocks = this.iterOverBlocks(entity.bbox) let chunk_index = 0 // iter over entity blocks - for (const block of blocks_iter) { + for (const entityBlock of entityBlocks) { const bufferStr = entity.data[chunk_index++] const buffer = bufferStr?.split(',').map(char => parseInt(char)) - const maxHeightDiff = entity.bbox.max.y - (block.localPos as Vector3).y - const entityBlockData = block + const maxHeightDiff = entity.bbox.max.y - (entityBlock.pos as Vector3).y + const entityBlockData = entityBlock entityBlockData.buffer = buffer?.slice(0, maxHeightDiff) || [] yield entityBlockData } @@ -292,7 +295,7 @@ export class BlocksContainer { // multi-pass chunk filling - const blockIterator = this.iterOverBlocks(undefined, true, false) + const blockIterator = this.iterOverBlocks(undefined, false) // ground blocks pass totalWrittenBlocks += ChunkFactory.default.fillGroundData( blockIterator, @@ -506,4 +509,90 @@ export class PatchContainer { getBlock(blockPos: Vector3) { return this.findPatch(blockPos)?.getBlock(blockPos, false) } + + getAllPatchesEntities(skipDuplicate = true) { + const entities: EntityChunk[] = [] + for (const patch of this.availablePatches) { + patch.entitiesChunks.forEach(entity => { + if (!skipDuplicate || !entities.find(ent => ent.bbox.equals(entity.bbox))) { + entities.push(entity) + } + }) + } + return entities + } + + // *iterAllPatchesBlocks() { + // for (const patch of this.availablePatches) { + // const blocks = patch.iterOverBlocks(undefined, false, false) + // for (const block of blocks) { + // yield block + // } + // } + // } + + // getMergedRows(zRowIndex: number) { + // const sortedPatchesRows = this.availablePatches + // .filter( + // patch => zRowIndex >= patch.bbox.min.z && zRowIndex <= patch.bbox.min.z, + // ) + // .sort((p1, p2) => p1.bbox.min.x - p2.bbox.min.x) + // .map(patch => patch.getBlocksRow(zRowIndex)) + // const mergedRows = sortedPatchesRows.reduce((arr1, arr2) => { + // const mergedArray = new Uint32Array(arr1.length + arr2.length) + // mergedArray.set(arr1) + // mergedArray.set(arr2, arr1.length) + // return mergedArray + // }) + // return mergedRows + // } + + // iterMergedRows() { + // const { min, max } = this.patchRange + // for (let zPatchIndex = min.z; zPatchIndex <= max.z; zPatchIndex++) { + // for (let zRowIndex = min.z; zRowIndex < max.z; zRowIndex++) {} + // } + // } + + // getMergedCols(xColIndex: number) { + + // } + + // mergedLinesIteration() { + // const { min, max } = this.bbox + // for (let x = min.x; x < max.x; x++) { + // for (let z = min.z; z < max.z; z++) { + + // } + // } + // } + + // toMergedContainer() { + // const mergedBox = this.availablePatches.map(patch => patch.bbox) + // .reduce((merge, bbox) => merge.union(bbox), new Box3()) + // // const mergedContainer = + // } + + // static fromMergedContainer() { + + // } + // mergeBlocks(blocksContainer: BlocksContainer) { + // // // for each patch override with blocks from blocks container + // this.availablePatches.forEach(patch => { + // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + // for (const target_block of blocksIter) { + // const source_block = blocksContainer.getBlock(target_block.pos, false) + // if (source_block && source_block.pos.y > 0 && target_block.index) { + // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE + // block_type = + // source_block.type === BlockType.TREE_TRUNK + // ? BlockType.TREE_TRUNK + // : block_type + // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y + // patch.writeBlock(target_block.index, block_level, block_type) + // // console.log(source_block?.pos.y) + // } + // } + // }) + // } } diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index 85515b5..a94a48e 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -231,55 +231,57 @@ export class RepeatableEntitiesMap extends EntitiesMap { }) } - override *iterate(input: Box3 | Vector3) { + getEntityInstance(entityTemplate: EntityData, mapShifting: Vector3) { + const mapLocalPos = entityTemplate.bbox.min + // switch to global position + const entityDims = entityTemplate.bbox.getSize(new Vector3()) + const bmin = mapShifting.clone().add(mapLocalPos.clone()) + const bmax = bmin.clone().add(entityDims) + const bbox = new Box3(bmin, bmax) + const entityInstance = { ...entityTemplate } + entityInstance.bbox = bbox + return entityInstance + } + + hasEntitySpawned(entityInstance: EntityData) { + const centerPos = entityInstance.bbox.getCenter(new Vector3()) + // eval spawn probability at entity center + const spawnProbabilty = this.probabilityEval(centerPos) + const entityId = centerPos.x + '_' + centerPos.z + const prng = alea(entityId) + const hasSpawned = prng() * spawnProbabilty < probabilityThreshold + return hasSpawned + } + + override *iterate(input: Box3 | Vector3, entityMask = (_entity: EntityData) => false) { if (this.entities.length === 0) RepeatableEntitiesMap.instance.populate() const { period } = this - const pos = input instanceof Box3 ? input.min : input + const realPos = input instanceof Box3 ? input.min : input const mapShift = new Vector3( - Math.floor(pos.x / period), + Math.floor(realPos.x / period), 0, - Math.floor(pos.z / period), + Math.floor(realPos.z / period), ).multiplyScalar(period) - - // find virtual map coords - const dims = + const mapDims = input instanceof Box3 ? input.getSize(new Vector3()) : new Vector3(1, 1, 1) - const point = input instanceof Box3 ? input.min : input - const mapPoint = new Vector3( - point.x % this.period, - 0, - point.z % this.period, - ) - mapPoint.x += mapPoint.x < 0 ? this.period : 0 - mapPoint.z += mapPoint.z < 0 ? this.period : 0 - const mapEnd = mapPoint.clone().add(dims) - mapEnd.y = 512 - const mapBox = new Box3(mapPoint, mapEnd) - + const mapVirtualStart = realPos.clone().sub(mapShift) + const mapVirtualEnd = mapVirtualStart.clone().add(mapDims) + mapVirtualStart.y = 0 + mapVirtualEnd.y = 512 + const mapVirtualBox = new Box3(mapVirtualStart, mapVirtualEnd) + // filter entities belonging to map area const entities = this.entities.filter(entity => - mapBox - ? entity.bbox.intersectsBox(mapBox) + mapVirtualBox + ? entity.bbox.intersectsBox(mapVirtualBox) : entity.bbox.containsPoint(input as Vector3), - ) - for (const entity of entities) { - const mapLocalPos = entity.bbox.min - // switch to global position - const entityDims = entity.bbox.getSize(new Vector3()) - const bmin = mapShift.clone().add(mapLocalPos.clone()) - const bmax = bmin.clone().add(entityDims) - const bbox = new Box3(bmin, bmax) - const centerPos = bbox.getCenter(new Vector3()) - // eval spawn probability at entity center - const spawnProbabilty = this.probabilityEval(centerPos) - const entityId = centerPos.x + '_' + centerPos.z - const prng = alea(entityId) - const hasSpawned = prng() * spawnProbabilty < probabilityThreshold - if (hasSpawned) { - const entityCopy = { ...entity } - entityCopy.bbox = bbox - yield entityCopy + ).filter(entity => !entityMask(entity))// discard entitie according to optional provided mask + + for (const entityTemplate of entities) { + const entityInstance = this.getEntityInstance(entityTemplate, mapShift) + if (this.hasEntitySpawned(entityInstance)) { + yield entityInstance } } } diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 262c0b5..98908a3 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -115,14 +115,15 @@ export class ChunkFactory { let writtenBlocksCount = 0 // iter over entity blocks for (const entityBlock of entityBlocksIterator) { - if (entityBlock.buffer && entityBlock.localPos) { - entityBlock.localPos.x += 1 - entityBlock.localPos.z += 1 + const entityLocalPos = entityBlock.localPos as Vector3 + if (entityBlock.buffer) { + entityLocalPos.x += 1 + entityLocalPos.z += 1 // bmin.y = block.localPos.y writtenBlocksCount += this.writeChunkBlocks( chunkData, chunkBox, - entityBlock.localPos, + entityLocalPos, entityBlock.data, entityBlock.buffer, ) From 7c57715bb940abe82a95ff0c37d411a5991979f7 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 28 Aug 2024 10:14:34 +0000 Subject: [PATCH 20/45] refactor: entities chunkification isolation --- src/api/world-compute.ts | 51 +++-------- src/common/types.ts | 16 +++- src/data/CacheContainer.ts | 7 +- src/data/DataContainers.ts | 179 +++++++++++++++++++------------------ src/procgen/EntitiesMap.ts | 67 +------------- src/tools/ChunkFactory.ts | 67 +++++++++++--- 6 files changed, 175 insertions(+), 212 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 61a3586..a9c8eef 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,10 +1,9 @@ -import { Box3, Vector3 } from 'three' +import { Vector3 } from 'three' -import { EntityType } from '../index' +import { ChunkFactory, EntityType } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' import { - EntitiesMap, EntityData, RepeatableEntitiesMap, } from '../procgen/EntitiesMap' @@ -12,14 +11,13 @@ import { BlockData, BlocksContainer, BlocksPatch, - EntityChunk, } from '../data/DataContainers' import { Block, PatchKey } from '../common/types' export const computePatch = (patchKey: PatchKey) => { const patch = new BlocksPatch(patchKey) genGroundBlocks(patch) - genEntitiesBlocks(patch) + genEntities(patch) return patch } @@ -71,7 +69,7 @@ export const computeGroundBlock = (blockPos: Vector3) => { } export const computeBlocksBuffer = (blockPos: Vector3) => { - let blocksBuffer: BlockType[] = [] + let blocksBuffer // query entities at current block const entitiesIter = RepeatableEntitiesMap.instance.iterate(blockPos) for (const entity of entitiesIter) { @@ -84,41 +82,19 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { if (entityType) { const entityLevel = Heightmap.instance.getGroundLevel(entityPos, rawVal) entity.bbox.min.y = entityLevel - entity.bbox.max.y = entityLevel + 10 + entity.bbox.max.y = entityLevel + 20 entity.type = entityType - blocksBuffer = EntitiesMap.fillBlockBuffer(blockPos, entity, blocksBuffer) + blocksBuffer = ChunkFactory.chunkifyEntity(entity, blockPos).data } } - return blocksBuffer + return blocksBuffer || [] } -const buildEntityChunk = (patch: BlocksContainer, entity: EntityData) => { - const entityChunk: EntityChunk = { - bbox: new Box3(), - data: [], - } - const blocksIter = patch.iterOverBlocks(entity.bbox) - for (const block of blocksIter) { - const blocksBuffer = EntitiesMap.fillBlockBuffer( - block.pos, - entity, - [], - ) - patch.bbox.max.y = Math.max( - patch.bbox.max.y, - (block.localPos as Vector3).y + blocksBuffer.length, - ) - const serialized = blocksBuffer - .reduce((str, val) => str + ',' + val, '') - .slice(1) - entityChunk.data.push(serialized) - entityChunk.bbox.expandByPoint(block.pos as Vector3) - } - entityChunk.bbox = entity.bbox - return entityChunk +export const bakeEntities = (_entities: EntityData) => { + // TODO } -const genEntitiesBlocks = (blocksContainer: BlocksContainer) => { +const genEntities = (blocksContainer: BlocksContainer) => { const entitiesIter = RepeatableEntitiesMap.instance.iterate( blocksContainer.bbox, ) @@ -134,10 +110,11 @@ const genEntitiesBlocks = (blocksContainer: BlocksContainer) => { // max.z % patch.dimensions.z + max.z >= 0 ? 0 : patch.dimensions.z) if (entityType) { entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos) - entity.bbox.max.y = entity.bbox.min.y + 10 + entity.bbox.max.y = entity.bbox.min.y + 20 entity.type = entityType - const entityChunk = buildEntityChunk(blocksContainer, entity) - blocksContainer.entitiesChunks.push(entityChunk) + blocksContainer.entities.push(entity) + // const entityChunk = buildEntityChunk(blocksContainer, entity) + // blocksContainer.entitiesChunks.push(entityChunk) // let item: BlockIteratorRes = blocksIter.next() } } diff --git a/src/common/types.ts b/src/common/types.ts index 7fe7894..02ee557 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,4 +1,4 @@ -import { Vector2, Vector3 } from 'three' +import { Box3, Vector2, Vector3 } from 'three' import { BlockData } from '../data/DataContainers' import { BiomeType, BlockType } from '../procgen/Biome' @@ -8,9 +8,12 @@ import { LinkedList } from './misc' export type Block = { pos: Vector3 data: BlockData - index?: number - localPos?: Vector3 - buffer?: BlockType[] + buffer?: Uint16Array +} + +export type PatchBlock = Block & { + index: number + localPos: Vector3 } export enum Adjacent2dPos { @@ -117,6 +120,11 @@ export type PatchId = Vector2 export type ChunkKey = string export type ChunkId = Vector3 +export type ChunkDataContainer = { + bbox: Box3 + data: Uint16Array +} + export type WorldChunk = { key: ChunkKey data: Uint16Array | null diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index 66cd88b..48b9731 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -4,7 +4,7 @@ import { PatchKey } from '../common/types' import { WorldConfig } from '../config/WorldConfig' import { WorldComputeApi } from '../index' -import { BlocksPatch, EntityChunk, PatchContainer } from './DataContainers' +import { BlocksPatch, PatchContainer } from './DataContainers' /** * Blocks cache @@ -16,11 +16,6 @@ export class CacheContainer extends PatchContainer { builtInCache = false // specify whether cache is managed internally or separately static cachePowRadius = 2 static cacheSize = WorldConfig.patchSize * 5 - // static worldApi = new WorldApi() - - // groundBlocks: Uint16Array = new Uint16Array(Math.pow(PatchBase.patchSize, 2)) - - entitiesChunks: EntityChunk[] = [] static get instance() { this.singleton = this.singleton || new CacheContainer() diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 20f1c27..1a8bef8 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -1,6 +1,6 @@ import { Box3, Vector2, Vector3 } from 'three' -import { Block, PatchKey, WorldChunk } from '../common/types' +import { Block, PatchBlock, PatchKey, WorldChunk, ChunkDataContainer, } from '../common/types' import { asVect3, computePatchKey, @@ -14,6 +14,7 @@ import { import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' +import { EntityData } from '../procgen/EntitiesMap' export enum BlockMode { DEFAULT, @@ -26,16 +27,11 @@ export type BlockData = { mode?: BlockMode } -export type EntityChunk = { - bbox: Box3 - data: string[] -} - export type PatchStub = { key: string bbox: Box3 rawDataContainer: Uint32Array - entitiesChunks: EntityChunk[] + entities: EntityData[] } // bits allocated per block data type @@ -58,7 +54,7 @@ export class BlocksContainer { margin = 0 rawDataContainer: Uint32Array - entitiesChunks: EntityChunk[] = [] + entities: EntityData[] = [] constructor(bbox: Box3, margin = 1) { this.bbox = bbox.clone() @@ -71,7 +67,7 @@ export class BlocksContainer { duplicate() { const copy = new BlocksContainer(this.bbox) this.rawDataContainer.forEach((v, i) => (copy.rawDataContainer[i] = v)) - copy.entitiesChunks = this.entitiesChunks + // copy.entitiesChunks = this.entitiesChunks return copy } @@ -127,7 +123,23 @@ export class BlocksContainer { get localExtendedBox() { return this.localBox.expandByScalar(this.margin) } - adjustRangeBox(rangeBox: Box3, local = false) { + + isWithinLocalRange(localPos: Vector3) { + return localPos.x >= 0 && + localPos.x < this.dimensions.x && + localPos.z >= 0 && + localPos.z < this.dimensions.z + } + + isWithinGlobalRange(globalPos: Vector3) { + return globalPos.x >= this.bbox.min.x && + globalPos.x < this.bbox.max.x && + globalPos.z >= this.bbox.min.z && + globalPos.z < this.bbox.max.z + } + + adjustRangeBox(rangeBox: Box3 | Vector3, local = false) { + rangeBox = rangeBox instanceof Box3 ? rangeBox : new Box3(rangeBox, rangeBox) const { min, max } = local ? this.localBox : this.bbox const rangeMin = new Vector3( Math.max(Math.floor(rangeBox.min.x), min.x), @@ -162,23 +174,22 @@ export class BlocksContainer { return origin.add(pos) } - getBlock(pos: Vector3, isLocalPos = true) { - const localPos = isLocalPos ? pos : this.toLocalPos(pos) - let block: Block | undefined - if ( - localPos.x >= 0 && - localPos.x < this.dimensions.x && - localPos.z >= 0 && - localPos.z < this.dimensions.z - ) { + getBlock(inputPos: Vector3, isLocalPos = true) { + const isWithingRange = isLocalPos ? this.isWithinLocalRange(inputPos) : + this.isWithinGlobalRange(inputPos) + let block: PatchBlock | undefined + if (isWithingRange) { + const localPos = isLocalPos ? inputPos : this.toLocalPos(inputPos) + const pos = isLocalPos ? this.toGlobalPos(inputPos) : inputPos const blockIndex = this.getBlockIndex(localPos) - const pos = isLocalPos ? localPos.clone() : this.toGlobalPos(localPos) - const data = this.readBlockData(blockIndex) - pos.y = data.level + const blockData = this.readBlockData(blockIndex) || BlockType.NONE + localPos.y = blockData.level + pos.y = blockData.level block = { - pos, + index: blockIndex, + pos: this.toGlobalPos(localPos), localPos, - data, + data: blockData, } } return block @@ -212,7 +223,7 @@ export class BlocksContainer { * @param rangeBox iteration range as global coords * @param skipMargin */ - *iterOverBlocks(rangeBox?: Box3, skipMargin = true) { + *iterOverBlocks(rangeBox?: Box3 | Vector3, skipMargin = true) { // convert to local coords to speed up iteration const localBbox = rangeBox ? this.adjustRangeBox(rangeBox) @@ -234,7 +245,7 @@ export class BlocksContainer { index = rangeBox ? this.getBlockIndex(localPos) : index const blockData = this.readBlockData(index) || BlockType.NONE localPos.y = blockData.level - const block: Block = { + const block: PatchBlock = { index, pos: this.toGlobalPos(localPos), localPos, @@ -247,22 +258,6 @@ export class BlocksContainer { } } - *iterEntityBlocks(entity: EntityChunk) { - // find overlapping blocks between entity and container - const entityBlocks = this.iterOverBlocks(entity.bbox) - let chunk_index = 0 - // iter over entity blocks - for (const entityBlock of entityBlocks) { - const bufferStr = entity.data[chunk_index++] - const buffer = bufferStr?.split(',').map(char => parseInt(char)) - const maxHeightDiff = entity.bbox.max.y - (entityBlock.pos as Vector3).y - const entityBlockData = entityBlock - entityBlockData.buffer = buffer?.slice(0, maxHeightDiff) || [] - yield entityBlockData - } - // } - } - containsBlock(blockPos: Vector3) { return ( blockPos.x >= this.bbox.min.x && @@ -272,42 +267,47 @@ export class BlocksContainer { ) } + *iterEntityChunkBlocks(entityChunk: ChunkDataContainer) { + // return overlapping blocks between entity and container + const entityDims = entityChunk.bbox.getSize(new Vector3()) + const blocks = this.iterOverBlocks(entityChunk.bbox) + + for (const block of blocks) { + // const buffer = entityChunk.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) + const chunkLocalPos = block.pos.clone().sub(entityChunk.bbox.min) + const buffIndex = chunkLocalPos.z * entityDims.x * entityDims.y + chunkLocalPos.x * entityDims.y + block.buffer = entityChunk.data.slice(buffIndex, buffIndex + entityDims.y) + const buffOffset = entityChunk.bbox.min.y - block.pos.y + const buffSrc = Math.abs(Math.min(0, buffOffset)) + const buffDest = Math.max(buffOffset, 0) + block.buffer = block.buffer?.copyWithin(buffDest, buffSrc) + block.buffer = buffOffset < 0 ? block.buffer?.fill(BlockType.NONE, buffOffset) : block.buffer + // block.buffer = new Array(20).fill(BlockType.TREE_TRUNK) + yield block + } + } + + // multi-pass chunk filling toChunk(chunkBox: Box3) { + let totalWrittenBlocks = 0 chunkBox = chunkBox || this.bbox const chunkDims = chunkBox.getSize(new Vector3()) const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) - let totalWrittenBlocks = 0 - // const debug_mode = true - - // const is_edge = (row, col, h, patch_size) => - // row === 1 || row === patch_size || col === 1 || col === patch_size - // || h === 1 - // || h === patch_size - 2 - - // const patch = PatchBlocksCache.instances.find( - // patch => - // patch.bbox.min.x === bbox.min.x + 1 && - // patch.bbox.min.z === bbox.min.z + 1 && - // patch.bbox.max.x === bbox.max.x - 1 && - // patch.bbox.max.z === bbox.max.z - 1 && - // patch.bbox.intersectsBox(bbox), - // ) - - // multi-pass chunk filling - - const blockIterator = this.iterOverBlocks(undefined, false) + // Ground pass + const groundBlocksIterator = this.iterOverBlocks(undefined, false) // ground blocks pass totalWrittenBlocks += ChunkFactory.default.fillGroundData( - blockIterator, + groundBlocksIterator, chunkData, chunkBox, ) - // entities blocks pass - for (const entity of this.entitiesChunks) { - const entityBlocksIterator = this.iterEntityBlocks(entity) - // overground entities pass - totalWrittenBlocks += ChunkFactory.default.fillEntitiesData( - entityBlocksIterator, + // Entities pass + for (const entity of this.entities) { + // const entityChunk = this.buildEntityChunk(entity) + const entityChunk = ChunkFactory.chunkifyEntity(entity) + const entityDataIterator = this.iterEntityChunkBlocks(entityChunk) //this.iterEntityBlocks(entity) + totalWrittenBlocks += ChunkFactory.default.mergeEntitiesData( + entityDataIterator, chunkData, chunkBox, ) @@ -324,10 +324,14 @@ export class BlocksContainer { } static fromStub(stub: any) { - const { rawDataContainer, entitiesChunks } = stub - const blocksContainer = new BlocksContainer(parseThreeStub(stub.bbox)) + const { rawDataContainer, entities, bbox } = stub + const blocksContainer = new BlocksContainer(parseThreeStub(bbox)) blocksContainer.rawDataContainer = rawDataContainer - blocksContainer.entitiesChunks = entitiesChunks + blocksContainer.entities = entities + .map((stub: EntityData) => ({ + ...stub, + bbox: parseThreeStub(stub.bbox), + })) // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => // patch.entitiesChunks.push(entityChunk), // ) @@ -353,29 +357,28 @@ export class BlocksPatch extends BlocksContainer { this.rawDataContainer.forEach( (rawVal, i) => (copy.rawDataContainer[i] = rawVal), ) - copy.entitiesChunks = this.entitiesChunks.map(entity => { - const entityCopy: EntityChunk = { - bbox: entity.bbox.clone(), - data: entity.data.slice(), - } - return entityCopy - }) + copy.entities = this.entities + .map(entity => { + const entityCopy: EntityData = { + ...entity, + bbox: entity.bbox.clone(), + } + return entityCopy + }) return copy } static override fromStub(patchStub: any) { - const { rawDataContainer, entitiesChunks } = patchStub + const { rawDataContainer, entities } = patchStub const bbox = parseThreeStub(patchStub.bbox) const patchKey = patchStub.key || computePatchKey(bbox) const patch = new BlocksPatch(patchKey) patch.rawDataContainer = rawDataContainer - patch.entitiesChunks = entitiesChunks.map((stub: EntityChunk) => { - const entityChunk: EntityChunk = { + patch.entities = entities + .map((stub: EntityData) => ({ + ...stub, bbox: parseThreeStub(stub.bbox), - data: stub.data, - } - return entityChunk - }) + })) patch.bbox.min.y = patchStub.bbox.min.y patch.bbox.max.y = patchStub.bbox.max.y // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => @@ -511,9 +514,9 @@ export class PatchContainer { } getAllPatchesEntities(skipDuplicate = true) { - const entities: EntityChunk[] = [] + const entities: EntityData[] = [] for (const patch of this.availablePatches) { - patch.entitiesChunks.forEach(entity => { + patch.entities.forEach(entity => { if (!skipDuplicate || !entities.find(ent => ent.bbox.equals(entity.bbox))) { entities.push(entity) } diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts index a94a48e..a4e93ed 100644 --- a/src/procgen/EntitiesMap.ts +++ b/src/procgen/EntitiesMap.ts @@ -1,8 +1,6 @@ import PoissonDiskSampling from 'poisson-disk-sampling' import alea from 'alea' import { Box3, Vector2, Vector3 } from 'three' - -import { TreeGenerators } from '../tools/TreeGenerator' import { EntityType, WorldConfig } from '../index' import { ProcLayer } from './ProcLayer' @@ -67,69 +65,6 @@ export class EntitiesMap { } } - /** - * Using precached tree data and block level to fill tree blocks buffer - * @param treeData - * @param blockLevel - * @param treeParams - * @returns - */ - static fillBlockBuffer( - blockPos: Vector3, - entity: EntityData, - buffer: BlockType[], - ) { - // const { treeRadius, treeSize } = entity.params - const treeRadius = 5 - const treeSize = 10 - const entityPos = entity.bbox.getCenter(new Vector3()) - entityPos.y = entity.bbox.min.y - const treeBuffer: BlockType[] = [] - const vDiff = blockPos.clone().sub(entityPos) - const offset = vDiff.y - const count = treeSize - offset - vDiff.y = 0 - const xzProj = vDiff.length() - if (xzProj && count > 0) { - // fill tree base - new Array(count) - .fill(BlockType.NONE) - .forEach(item => treeBuffer.push(item)) - // tree foliage - for (let y = -treeRadius; y < treeRadius; y++) { - const blockType = TreeGenerators[entity.type as EntityType]( - xzProj, - y, - treeRadius, - ) - treeBuffer.push(blockType) - } - } else { - try { - // a bit of an hack for now => TODO: find good fix - new Array(count + treeRadius - Math.floor(treeSize * 0.4)) - .fill(BlockType.TREE_TRUNK) - .forEach(item => treeBuffer.push(item)) - } catch { - // console.log(error) - } - } - const sum = treeBuffer.reduce((sum, val) => sum + val, 0) - if (sum > 0) { - treeBuffer.forEach((elt, i) => { - const current = buffer[i] - if (current !== undefined) { - buffer[i] = !buffer[i] ? elt : current - } else { - buffer.push(elt) - } - }) - } - const res = sum > 0 ? treeBuffer : [] - entity.bbox.max.y = entity.bbox.min.y + 20 // res.length - return res - } - /** * Randomly spawn entites according to custom distribution */ @@ -276,7 +211,7 @@ export class RepeatableEntitiesMap extends EntitiesMap { mapVirtualBox ? entity.bbox.intersectsBox(mapVirtualBox) : entity.bbox.containsPoint(input as Vector3), - ).filter(entity => !entityMask(entity))// discard entitie according to optional provided mask + ).filter(entity => !entityMask(entity))// discard entities according to optional provided mask for (const entityTemplate of entities) { const entityInstance = this.getEntityInstance(entityTemplate, mapShift) diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 98908a3..8711514 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,9 +1,11 @@ -import { Box3, MathUtils, Vector3 } from 'three' +import { Box3, MathUtils, Vector2, Vector3 } from 'three' -import { Block, PatchId } from '../common/types' -import { asVect3 } from '../common/utils' -import { BlockData } from '../data/DataContainers' -import { BlockMode, BlockType } from '../index' +import { PatchBlock, PatchId } from '../common/types' +import { asVect2, asVect3 } from '../common/utils' +import { BlockData, BlockMode } from '../data/DataContainers' +import { BlockType } from '../index' +import { EntityData } from '../procgen/EntitiesMap' +import { TreeGenerators } from './TreeGenerator' const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE @@ -41,7 +43,7 @@ export class ChunkFactory { chunkBbox: Box3, blockLocalPos: Vector3, blockData: BlockData, - bufferOver: any[] = [], + bufferOver: Uint16Array | [], ) { const chunk_size = chunkBbox.getSize(new Vector3()).x // Math.round(Math.pow(chunkDataContainer.length, 1 / 3)) @@ -82,7 +84,7 @@ export class ChunkFactory { } fillGroundData( - blockIterator: Generator, + blockIterator: Generator, chunkDataContainer: Uint16Array, chunkBox: Box3, ) { @@ -101,20 +103,20 @@ export class ChunkFactory { chunkBox, blockLocalPos, blockData, - block.buffer, + block.buffer || [], ) } return written_blocks_count } - fillEntitiesData( - entityBlocksIterator: Generator, + mergeEntitiesData( + entityDataIterator: Generator, chunkData: Uint16Array, chunkBox: Box3, ) { let writtenBlocksCount = 0 // iter over entity blocks - for (const entityBlock of entityBlocksIterator) { + for (const entityBlock of entityDataIterator) { const entityLocalPos = entityBlock.localPos as Vector3 if (entityBlock.buffer) { entityLocalPos.x += 1 @@ -132,6 +134,49 @@ export class ChunkFactory { return writtenBlocksCount } + static chunkifyEntity(entity: EntityData, blockPosOrRange?: Vector3 | Box3) { + if (blockPosOrRange instanceof Vector3) { + const blockStart = new Vector3(blockPosOrRange.x, entity.bbox.min.y, blockPosOrRange.z) + const blockEnd = blockStart.clone().add(new Vector3(1, entity.bbox.max.y - entity.bbox.min.y, 1)) + blockPosOrRange = new Box3(blockStart, blockEnd) + } + const range = blockPosOrRange || entity.bbox + const dims = range.getSize(new Vector3()) + const data = new Uint16Array(dims.z * dims.x * dims.y) + const { size: treeSize, radius: treeRadius } = entity.params + const entityPos = entity.bbox.getCenter(new Vector3()) + let index = 0 + for (let { z } = range.min; z < range.max.z; z++) { + for (let { x } = range.min; x < range.max.x; x++) { + for (let { y } = range.min; y < range.max.y; y++) { + const xzProj = new Vector2(x, z).sub(asVect2(entityPos)) + if (xzProj.length() > 0) { + if (y < range.min.y + treeSize) { + // empty space around trunk between ground and trunk top + data[index++] = BlockType.NONE + } else { + // tree foliage + const blockType = TreeGenerators[entity.type]( + xzProj.length(), + y - (range.min.y + treeSize + treeRadius), + treeRadius, + ) + data[index++] = blockType + } + } else { + // tree trunk + data[index++] = BlockType.TREE_TRUNK + } + } + } + } + const entityChunk = { + bbox: range, + data + } + return entityChunk + } + genChunksIdsFromPatchId(patchId: PatchId) { const { ymin, ymax } = this.chunksRange const chunk_ids = [] From 0b07176d44f256ca2316af6fff8a3bab3d0a1d7a Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 28 Aug 2024 17:26:24 +0000 Subject: [PATCH 21/45] refactor: generic distribution map --- src/api/world-compute.ts | 86 +++++------ src/common/types.ts | 9 ++ src/common/utils.ts | 12 +- src/data/BoardContainer.ts | 91 +++++++++--- src/data/DataContainers.ts | 3 +- src/index.ts | 3 +- src/procgen/DistributionMap.ts | 221 ++++++++++++++++++++++++++++ src/procgen/EntitiesMap.ts | 257 --------------------------------- src/tools/ChunkFactory.ts | 4 +- 9 files changed, 362 insertions(+), 324 deletions(-) create mode 100644 src/procgen/DistributionMap.ts delete mode 100644 src/procgen/EntitiesMap.ts diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index a9c8eef..f7585ad 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,18 +1,18 @@ -import { Vector3 } from 'three' +import { Box2, Box3, Vector2, Vector3 } from 'three' -import { ChunkFactory, EntityType } from '../index' +import { ChunkFactory, EntityType, PseudoRandomDistMap } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { - EntityData, - RepeatableEntitiesMap, -} from '../procgen/EntitiesMap' import { BlockData, BlocksContainer, BlocksPatch, } from '../data/DataContainers' -import { Block, PatchKey } from '../common/types' +import { Block, EntityData, PatchKey } from '../common/types' +import { asBox2, asVect2, asVect3 } from '../common/utils' + +// TODO remove hardcoded entity dimensions to compute from entity type +const entityDefaultDims = new Vector3(10, 20, 10) export const computePatch = (patchKey: PatchKey) => { const patch = new BlocksPatch(patchKey) @@ -68,24 +68,38 @@ export const computeGroundBlock = (blockPos: Vector3) => { return block } +const genEntity = (entityPos: Vector3) => { + let entity: EntityData | undefined + // 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 + if (entityType) { + entityPos.y = Heightmap.instance.getGroundLevel(entityPos, rawVal) + entityDefaultDims.y / 2 + const entityBox = new Box3().setFromCenterAndSize(entityPos, entityDefaultDims) + entity = { + type: entityType, + bbox: entityBox, + params: { + radius: 5, + size: 10 + } + } + } + return entity +} + export const computeBlocksBuffer = (blockPos: Vector3) => { let blocksBuffer // query entities at current block - const entitiesIter = RepeatableEntitiesMap.instance.iterate(blockPos) - for (const entity of entitiesIter) { - // use global coords in case entity center is from adjacent patch - const entityPos = entity.bbox.getCenter(new Vector3()) - 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 - if (entityType) { - const entityLevel = Heightmap.instance.getGroundLevel(entityPos, rawVal) - entity.bbox.min.y = entityLevel - entity.bbox.max.y = entityLevel + 20 - entity.type = entityType - blocksBuffer = ChunkFactory.chunkifyEntity(entity, blockPos).data - } + const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) + const mapPos = asVect2(blockPos) + const mapItems = PseudoRandomDistMap.instance.iterMapItems(entityShaper, mapPos) + for (const mapPos of mapItems) { + const entityPos = asVect3(mapPos) + const entity = genEntity(entityPos) + blocksBuffer = entity ? ChunkFactory.chunkifyEntity(entity, blockPos).data : blocksBuffer } return blocksBuffer || [] } @@ -95,27 +109,17 @@ export const bakeEntities = (_entities: EntityData) => { } const genEntities = (blocksContainer: BlocksContainer) => { - const entitiesIter = RepeatableEntitiesMap.instance.iterate( - blocksContainer.bbox, - ) - for (const entity of entitiesIter) { + // query entities on patch range + const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type + const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) + const mapBox = asBox2(blocksContainer.bbox) + const entitiesIter = PseudoRandomDistMap.instance.iterMapItems(entityShaper, mapBox) + for (const mapPos of entitiesIter) { // use global coords in case entity center is from adjacent patch - const entityPos = entity.bbox.getCenter(new Vector3()) - const biome = Biome.instance.getMainBiome(entityPos) - const rawVal = Heightmap.instance.getRawVal(entityPos) - const blockTypes = Biome.instance.getBlockType(rawVal, biome) - const entityType = blockTypes.entities?.[0] as EntityType - // const patchLocalBmin = new Vector3(min.x % patch.dimensions.x + min.x >= 0 ? 0 : patch.dimensions.x, - // 0, - // max.z % patch.dimensions.z + max.z >= 0 ? 0 : patch.dimensions.z) - if (entityType) { - entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos) - entity.bbox.max.y = entity.bbox.min.y + 20 - entity.type = entityType + const entityPos = asVect3(mapPos) + const entity = genEntity(entityPos) + if (entity) { blocksContainer.entities.push(entity) - // const entityChunk = buildEntityChunk(blocksContainer, entity) - // blocksContainer.entitiesChunks.push(entityChunk) - // let item: BlockIteratorRes = blocksIter.next() } } } diff --git a/src/common/types.ts b/src/common/types.ts index 02ee557..b38a422 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -115,6 +115,15 @@ export enum EntityType { TREE_PINE = 'pine_tree', } +export type EntityData = { + type: EntityType + bbox: Box3 + params: { + radius: 5 + size: 10 + } +} + export type PatchKey = string export type PatchId = Vector2 export type ChunkKey = string diff --git a/src/common/utils.ts b/src/common/utils.ts index 412988b..cd8d598 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,4 +1,4 @@ -import { Box3, Vector2, Vector3, Vector3Like } from 'three' +import { Box2, Box3, Vector2, Vector3, Vector3Like } from 'three' import { WorldConfig } from '../config/WorldConfig' @@ -209,6 +209,14 @@ const asVect3 = (v2: Vector2, yVal = 0) => { return new Vector3(v2.x, yVal, v2.y) } +const asBox2 = (box3: Box3) => { + return new Box2(asVect2(box3.min), asVect2(box3.max)) +} + +const asBox3 = (box2: Box2) => { + return new Box3(asVect3(box2.min), asVect3(box2.max)) +} + const parseVect3Stub = (stub: Vector3Like) => { let res if (isVect3Stub(stub)) { @@ -340,6 +348,8 @@ export { parseThreeStub, asVect2, asVect3, + asBox2, + asBox3, parsePatchKey, convertPosToPatchId, computePatchKey, diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index 683c4b8..af7c9db 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,10 +1,14 @@ import { Box3, Vector3 } from 'three' -import { Block } from '../common/types' +import { EntityData, PatchBlock } from '../common/types' import { asVect2 } from '../common/utils' +import { BlockData, BlockMode, PatchContainer } from './DataContainers' import { WorldCacheContainer } from '../index' -import { BlockData, BlockMode, PatchContainer } from './DataContainers' +export type BoardStub = { + bbox: Box3, + data: BlockData +} export class BoardContainer extends PatchContainer { boardCenter @@ -21,7 +25,17 @@ export class BoardContainer extends PatchContainer { this.initFromBoxAndMask(this.bbox) } - isInsideBoardFilter(blockPos: Vector3) { + restoreOriginalPatches() { + const original_patches_container = new PatchContainer() + original_patches_container.initFromBoxAndMask(this.bbox) + original_patches_container.populateFromExisting( + WorldCacheContainer.instance.availablePatches, + true, + ) + return original_patches_container + } + + filterBoardBlocks(blockPos: Vector3) { let isInsideBoard = false if (blockPos) { const heightDiff = Math.abs(blockPos.y - this.boardCenter.y) @@ -31,7 +45,7 @@ export class BoardContainer extends PatchContainer { return isInsideBoard } - overrideBlock(block: Block) { + overrideBlock(block: PatchBlock) { const blockData = block.data blockData.level = this.boardCenter.y blockData.mode = BlockMode.BOARD_CONTAINER @@ -50,7 +64,7 @@ export class BoardContainer extends PatchContainer { // const blocks = this.iterPatchesBlocks() for (const block of blocks) { // discard blocs not included in board shape - if (this.isInsideBoardFilter(block.pos)) { + if (this.filterBoardBlocks(block.pos)) { const boardBlock = this.overrideBlock(block) patch.writeBlockData(boardBlock.index, boardBlock.data) this.bbox.expandByPoint(boardBlock.pos) @@ -64,31 +78,45 @@ export class BoardContainer extends PatchContainer { const boardEntities = this.getAllPatchesEntities() .filter(ent => { const entityCenter = ent.bbox.getCenter(new Vector3()) - return this.isInsideBoardFilter(entityCenter) + return this.filterBoardBlocks(entityCenter) }) return boardEntities } trimTrees() { this.availablePatches.forEach(patch => { - patch.entitiesChunks.forEach(entity => { + patch.entities.forEach(entity => { const entityCenter = entity.bbox.getCenter(new Vector3()) const entityCenterBlock = this.getBlock(entityCenter) entityCenter.y = entity.bbox.min.y const isEntityOverlappingBoard = () => { - const entityBlocks = patch.iterEntityBlocks(entity) + const entityBlocks = patch.iterOverBlocks(entity.bbox) for (const block of entityBlocks) { - if (this.isInsideBoardFilter(block.pos)) { + if (this.filterBoardBlocks(block.pos)) { return true } } return false } - if (entityCenterBlock && this.isInsideBoardFilter(entityCenterBlock.pos)) { + if (entityCenterBlock && this.filterBoardBlocks(entityCenterBlock.pos)) { // trim entities belonging to board const diff = entityCenter.clone().sub(this.boardCenter) - entity.bbox.max.y = entity.bbox.min.y - diff.y + 2 + // const radius = 3 + // const entityCenterPos = entityCenterBlock.pos + // entity.bbox.min.x = entityCenterPos.x - radius + // entity.bbox.max.x = entityCenterPos.x + radius + // entity.bbox.min.z = entityCenterPos.z - radius + // entity.bbox.max.z = entityCenterPos.z + radius + entity.bbox.max.y = entity.bbox.min.y + 2 - Math.min(diff.y, 0) + // const entityBlocks = patch.iterEntityBlocks(entity) + // // check if a block is outside the board and belongs to another patch + // for (const block of entityBlocks) { + // if (!this.isInsideBoard(block) && !this.findPatch(block.pos)?.bbox.equals(patch.bbox)) { + // // discard entity + // entity.bbox.makeEmpty() + // } + // } } else if (isEntityOverlappingBoard()) { // discard outside entities having an overlap with the board entity.bbox.makeEmpty() @@ -98,15 +126,38 @@ export class BoardContainer extends PatchContainer { }) } - restoreOriginalPatches() { - const original_patches_container = new PatchContainer() - original_patches_container.initFromBoxAndMask(this.bbox) - original_patches_container.populateFromExisting( - WorldCacheContainer.instance.availablePatches, - true, - ) - return original_patches_container + genStartPosEntities() { + const existingBoardEntities = this.getBoardEntities() + // discard entities from spawning over existing entities + const discardEntity = (entity: EntityData) => existingBoardEntities + .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) + // RepeatableEntitiesMap.instance. + } + + genHoleEntities() { + + } + + highlightStartPos() { + + } + + digHoles() { + + } + + exportStub() { + const origin = this.bbox.min.clone() + const dimensions = this.bbox.getSize(new Vector3()) + const { x, z } = dimensions + const size = { x, z } + // const data = + // const boardData = { + // origin, + // size, + // data + // } } - smoothEdges() {} + smoothEdges() { } } diff --git a/src/data/DataContainers.ts b/src/data/DataContainers.ts index 1a8bef8..ac79103 100644 --- a/src/data/DataContainers.ts +++ b/src/data/DataContainers.ts @@ -1,6 +1,6 @@ import { Box3, Vector2, Vector3 } from 'three' -import { Block, PatchBlock, PatchKey, WorldChunk, ChunkDataContainer, } from '../common/types' +import { Block, PatchBlock, PatchKey, WorldChunk, ChunkDataContainer, EntityData, } from '../common/types' import { asVect3, computePatchKey, @@ -14,7 +14,6 @@ import { import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' -import { EntityData } from '../procgen/EntitiesMap' export enum BlockMode { DEFAULT, diff --git a/src/index.ts b/src/index.ts index bd13f40..d99372a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ + export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' +export { PseudoRandomDistMap, RandomDistributionMap } from './procgen/DistributionMap' export { BlocksContainer, BlocksPatch, @@ -9,7 +11,6 @@ export { } from './data/DataContainers' export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' -export { EntitiesMap, RepeatableEntitiesMap } from './procgen/EntitiesMap' export { EntityType } from './common/types' export { CacheContainer as WorldCacheContainer } from './data/CacheContainer' export { ChunkFactory } from './tools/ChunkFactory' diff --git a/src/procgen/DistributionMap.ts b/src/procgen/DistributionMap.ts new file mode 100644 index 0000000..d5ccbe9 --- /dev/null +++ b/src/procgen/DistributionMap.ts @@ -0,0 +1,221 @@ +import PoissonDiskSampling from 'poisson-disk-sampling' +import alea from 'alea' +import { Box2, Vector2, Vector3 } from 'three' + +import { ProcLayer } from './ProcLayer' +import { EntityData } from '../common/types' +// import { Adjacent2dPos } from '../common/types' +// import { getAdjacent2dCoords } from '../common/utils' + +const probabilityThreshold = Math.pow(2, 8) +const distMapDefaults = { + minDistance: 8, + maxDistance: 100, + tries: 20, +} + +/** + * Map for querying/iterating randomly distributed items + * at block level or from custom box range + */ +export class RandomDistributionMap { + bbox: Box2 + params + points: Vector2[] = [] + densityMap = new ProcLayer('treemap') + + constructor(bbox: Box2, distParams: any = {}) { + this.bbox = bbox + this.params = distParams + } + + get dimensions() { + return this.bbox.getSize(new Vector2()) + } + + populate() { + const { dimensions, params } = this + const prng = alea('RandomDistributionMap') + const p = new PoissonDiskSampling( + { + shape: [dimensions.x, dimensions.y], + ...params + }, + prng, + ) + this.points = p.fill() + .map(point => + new Vector2(point[0] as number, point[1] as number).round()) + } + + spawnProbabilityEval(pos: Vector2) { + const maxCount = 1 // 16 * Math.round(Math.exp(10)) + const val = this.densityMap?.eval(pos) + const adjustedVal = val + ? (16 * Math.round(Math.exp((1 - val) * 10))) / maxCount + : 0 + return adjustedVal + } + + hasSpawned(itemPos: Vector2) { + // eval spawn probability at entity center + const spawnProbabilty = this.spawnProbabilityEval(itemPos) + const itemId = itemPos.x + ':' + itemPos.y + const prng = alea(itemId) + const hasSpawned = prng() * spawnProbabilty < probabilityThreshold + return hasSpawned + } + + /** + * all entities belonging or overlapping with given box area + * or all entities found at given block position + * @param patchCoords + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, + pointOrRange: Vector2 | Box2, + // spawnProbabilityOverride: (entityPos?: Vector2) => number, + ) { + // return entities in patch local coords + for (const entityPos of this.points) { + const entityShape = entityShaper(entityPos) + const isWithinRange = pointOrRange instanceof Box2 ? + entityShape.intersectsBox(pointOrRange) : entityShape.containsPoint(pointOrRange) + // TODO eval entity spawn probability here + if (isWithinRange && this.hasSpawned(entityPos)) + yield entityPos + } + } + + + // /** + // * Randomly spawn entites according to custom distribution + // */ + // static spawnEntity(pos: Vector2) { + // // return Math.sin(0.01 * pos.x * pos.y) > 0.99 + // const offset = 10 + // return pos.x % 20 === offset && pos.y % 20 === offset + // } +} + +/** + * Infinite map using repeatable seamless pattern providing + * independant, deterministic and approximated random distribution + */ +export class PseudoRandomDistMap extends RandomDistributionMap { + // eslint-disable-next-line no-use-before-define + static singleton: PseudoRandomDistMap + + constructor(mapPeriod: number, params = distMapDefaults) { + super(new Box2(new Vector2(0, 0), new Vector2(mapPeriod, mapPeriod)), params) + } + + static get instance() { + return PseudoRandomDistMap.singleton + } + + static set instance(defaultInstance: PseudoRandomDistMap) { + PseudoRandomDistMap.singleton = defaultInstance + } + + override populate() { + super.populate() + // make seamless repeatable map + const { dimensions } = this + const radius = this.params.minDistance / 2 + const edgePoints = this.points + .map(point => { + const pointCopy = point.clone() + if (point.x - radius < 0) { + pointCopy.x += dimensions.x + } else if (point.x + radius > dimensions.x) { + pointCopy.x -= dimensions.x + } + if (point.y - radius < 0) { + pointCopy.y += dimensions.y + } else if (point.y + radius > dimensions.y) { + pointCopy.y -= dimensions.y + } + return pointCopy.round().equals(point) ? null : pointCopy + }) + .filter(pointCopy => pointCopy) + edgePoints.forEach(edgePoint => edgePoint && this.points.push(edgePoint)) + } + + /** + * Either whole area or individual point overlapping with entity + * @param input + * @param entityMask + */ + override *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, + inputPointOrRange: Vector2 | Box2, + // spawnProbabilityOverride: (entityPos?: Vector2) => number, + // entityMask = (_entity: EntityData) => false + ) { + const { dimensions } = this + const mapOrigin = inputPointOrRange instanceof Box2 ? inputPointOrRange.min : inputPointOrRange + const mapShifting = new Vector2( + Math.floor(mapOrigin.x / dimensions.x), + Math.floor(mapOrigin.y / dimensions.y), + ).multiply(dimensions) + const mapDims = + inputPointOrRange instanceof Box2 + ? inputPointOrRange.getSize(new Vector2()) + : new Vector2(1, 1) + const virtualMapStart = mapOrigin.clone().sub(mapShifting) + const virtualMapEnd = virtualMapStart.clone().add(mapDims) + const virtualMapBox = new Box2(virtualMapStart, virtualMapEnd) + const toRealMapPos = (virtualMapRelativePos: Vector2) => + mapShifting.clone().add(virtualMapRelativePos) + + // filter all items belonging to map area or intersecting point + const pointCandidates = this.points + .filter(entityCenter => { + const entityBox = entityShaper(entityCenter) + return virtualMapBox ? entityBox.intersectsBox(virtualMapBox) + : entityBox.containsPoint(mapOrigin) + }) + // .filter(entity => !entityMask(entity))// discard entities according to optional provided mask + + for (const entityCenter of pointCandidates) { + const entityPos = toRealMapPos(entityCenter) + if (this.hasSpawned(entityPos)) { + yield entityPos + } + } + } +} + +/** + * Storing entities at biome level with overlap at biomes' transitions + */ +export class OverlappingEntitiesMap extends RandomDistributionMap { + // entities stored per biome + static biomeMapsLookup: Record = {} + // getAdjacentEntities() { + // const adjacentEntities = [] + // const adjacentKeys = Object.values(Adjacent2dPos) + // .filter(v => !isNaN(Number(v)) && v !== Adjacent2dPos.center) + // .map(adjKey => { + // const adjCoords = getAdjacent2dCoords(patchCoords, adjKey as Adjacent2dPos) + // const mapKey = `map_${adjCoords.x % repeatPeriod}_${adjCoords.y % repeatPeriod}` + // return mapKey + // }) + // 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 + // // if block or patch contained withing unique biome, return matching entities + // // else if overlapping across several biomes, compute transition + // } +} diff --git a/src/procgen/EntitiesMap.ts b/src/procgen/EntitiesMap.ts deleted file mode 100644 index a4e93ed..0000000 --- a/src/procgen/EntitiesMap.ts +++ /dev/null @@ -1,257 +0,0 @@ -import PoissonDiskSampling from 'poisson-disk-sampling' -import alea from 'alea' -import { Box3, Vector2, Vector3 } from 'three' -import { EntityType, WorldConfig } from '../index' - -import { ProcLayer } from './ProcLayer' -import { BlockType } from './Biome' -// import { Adjacent2dPos } from '../common/types' -// import { getAdjacent2dCoords } from '../common/utils' - -export type EntityData = { - // xzProj: number - level: number - type: EntityType - bbox: Box3 - edgesOverlaps?: any - params: { - radius: 5 - size: 10 - } -} - -const probabilityThreshold = Math.pow(2, 8) - -/** - * Common interface for querying/iterating entities at block or patch level - * Custom implementation left to child class - */ -export class EntitiesMap { - static density = new ProcLayer('treemap') - - probabilityEval(pos: Vector3) { - const maxCount = 1 // 16 * Math.round(Math.exp(10)) - const val = EntitiesMap.density?.eval(pos) - const adjustedVal = val - ? (16 * Math.round(Math.exp((1 - val) * 10))) / maxCount - : 0 - return adjustedVal - } - - mergeBuffers(srcBuffer: BlockType[], dstBuffer: BlockType[]) { - // console.log(`merging buffers: `, srcBuffer, dstBuffer) - const merged = [] - srcBuffer.reverse() - dstBuffer.reverse() - while (srcBuffer.length || dstBuffer.length) { - const val = srcBuffer.pop() || dstBuffer.pop() - merged.push(val) - } - // console.log(`result: `, merged) - return merged - } - - /** - * all entities belonging or overlapping with given box area - * or all entities found at given block position - * @param patchCoords - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - *iterate(_input: Box3 | Vector3) { - const entities: EntityData[] = [] - // return entities in patch local coords - for (const entity of entities) { - yield entity - } - } - - /** - * Randomly spawn entites according to custom distribution - */ - static spawnEntity(pos: Vector2) { - // return Math.sin(0.01 * pos.x * pos.y) > 0.99 - const offset = 10 - return pos.x % 20 === offset && pos.y % 20 === offset - } -} - -/** - * Storing entities on repeatable seamless pattern - */ -export class RepeatableEntitiesMap extends EntitiesMap { - // eslint-disable-next-line no-use-before-define - static singleton: RepeatableEntitiesMap - static get instance() { - RepeatableEntitiesMap.singleton = - RepeatableEntitiesMap.singleton || new RepeatableEntitiesMap() - return RepeatableEntitiesMap.singleton - } - - // entities stored in pattern - entities: EntityData[] = [] - // period of repeated pattern - period - constructor() { - super() - this.period = WorldConfig.patchSize * 2 - } - - // one time init of repeatable pattern - populate() { - const { period } = this - const prng = alea('poisson_disk_sampling') - const p = new PoissonDiskSampling( - { - shape: [period, period], - minDistance: 8, - maxDistance: 100, - tries: 20, - // distanceFunction: function (p) { - // return getImagePixelValueSomehow(p[0], p[1]); // value between 0 and 1 - // } - }, - prng, - ) - const points = p.fill() - this.entities = points.map(point => { - const mapPos = new Vector3( - Math.round(point[0] as number), - 0, - Math.round(point[1] as number), - ) - // const mapKey = `map_${Math.floor(pos.x / patchSize)}_${Math.floor(pos.y / patchSize)}` - // const localPos = new Vector3(pos.x % patchSize, 0, pos.y % patchSize) - const dimensions = new Vector3(10, 0, 10) - const bbox = new Box3().setFromCenterAndSize(mapPos, dimensions) - const type = EntityType.NONE - const entity: EntityData = { - level: 0, - type, - bbox, - params: { - radius: 5, - size: 10, - }, - } - return entity - }) - const edgeEntities = this.entities.filter( - entity => - entity.bbox.min.x < 0 || - entity.bbox.min.z < 0 || - entity.bbox.max.x > period || - entity.bbox.max.z > period, - ) - edgeEntities.map(entity => { - const bmin = entity.bbox.min.clone() - const bmax = entity.bbox.max.clone() - if (bmin.x < 0) { - bmin.x += period - bmax.x += period - } else if (bmax.x > period) { - bmin.x -= period - bmax.x -= period - } - if (bmin.z < 0) { - bmin.z += period - bmax.z += period - } else if (bmax.z > period) { - bmin.z -= period - bmax.z -= period - } - const entityCopy = { ...entity } - entityCopy.bbox = new Box3(bmin, bmax) - this.entities.push(entityCopy) - return entityCopy - }) - } - - getEntityInstance(entityTemplate: EntityData, mapShifting: Vector3) { - const mapLocalPos = entityTemplate.bbox.min - // switch to global position - const entityDims = entityTemplate.bbox.getSize(new Vector3()) - const bmin = mapShifting.clone().add(mapLocalPos.clone()) - const bmax = bmin.clone().add(entityDims) - const bbox = new Box3(bmin, bmax) - const entityInstance = { ...entityTemplate } - entityInstance.bbox = bbox - return entityInstance - } - - hasEntitySpawned(entityInstance: EntityData) { - const centerPos = entityInstance.bbox.getCenter(new Vector3()) - // eval spawn probability at entity center - const spawnProbabilty = this.probabilityEval(centerPos) - const entityId = centerPos.x + '_' + centerPos.z - const prng = alea(entityId) - const hasSpawned = prng() * spawnProbabilty < probabilityThreshold - return hasSpawned - } - - override *iterate(input: Box3 | Vector3, entityMask = (_entity: EntityData) => false) { - if (this.entities.length === 0) RepeatableEntitiesMap.instance.populate() - const { period } = this - const realPos = input instanceof Box3 ? input.min : input - const mapShift = new Vector3( - Math.floor(realPos.x / period), - 0, - Math.floor(realPos.z / period), - ).multiplyScalar(period) - const mapDims = - input instanceof Box3 - ? input.getSize(new Vector3()) - : new Vector3(1, 1, 1) - const mapVirtualStart = realPos.clone().sub(mapShift) - const mapVirtualEnd = mapVirtualStart.clone().add(mapDims) - mapVirtualStart.y = 0 - mapVirtualEnd.y = 512 - const mapVirtualBox = new Box3(mapVirtualStart, mapVirtualEnd) - // filter entities belonging to map area - const entities = this.entities.filter(entity => - mapVirtualBox - ? entity.bbox.intersectsBox(mapVirtualBox) - : entity.bbox.containsPoint(input as Vector3), - ).filter(entity => !entityMask(entity))// discard entities according to optional provided mask - - for (const entityTemplate of entities) { - const entityInstance = this.getEntityInstance(entityTemplate, mapShift) - if (this.hasEntitySpawned(entityInstance)) { - yield entityInstance - } - } - } -} - -/** - * Storing entities at biome level with overlap at biomes' transitions - */ -export class OverlappingEntitiesMap extends EntitiesMap { - // entities stored per biome - static biomeMapsLookup: Record = {} - // getAdjacentEntities() { - // const adjacentEntities = [] - // const adjacentKeys = Object.values(Adjacent2dPos) - // .filter(v => !isNaN(Number(v)) && v !== Adjacent2dPos.center) - // .map(adjKey => { - // const adjCoords = getAdjacent2dCoords(patchCoords, adjKey as Adjacent2dPos) - // const mapKey = `map_${adjCoords.x % repeatPeriod}_${adjCoords.y % repeatPeriod}` - // return mapKey - // }) - // const adjacentMaps = adjacentKeys.map(mapKey => EntitiesMap.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 - // // if block or patch contained withing unique biome, return matching entities - // // else if overlapping across several biomes, compute transition - // } -} diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 8711514..1d95d64 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,10 +1,10 @@ import { Box3, MathUtils, Vector2, Vector3 } from 'three' -import { PatchBlock, PatchId } from '../common/types' +import { EntityData, PatchBlock, PatchId } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockData, BlockMode } from '../data/DataContainers' import { BlockType } from '../index' -import { EntityData } from '../procgen/EntitiesMap' + import { TreeGenerators } from './TreeGenerator' const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE From d65f511d50af5dee1939ef8bed6879899f03f153 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 29 Aug 2024 07:58:07 +0000 Subject: [PATCH 22/45] wip: start pos distribution + refactor data containers --- src/api/WorldComputeApi.ts | 2 +- src/api/world-compute.ts | 11 +- src/common/types.ts | 2 +- src/config/WorldConfig.ts | 1 + ...{DataContainers.ts => BlocksContainers.ts} | 33 +++-- src/data/BoardContainer.ts | 34 ++++- src/data/CacheContainer.ts | 6 +- src/index.ts | 6 +- src/procgen/DistributionMap.ts | 128 ++++++------------ src/tools/ChunkFactory.ts | 4 +- 10 files changed, 111 insertions(+), 116 deletions(-) rename src/data/{DataContainers.ts => BlocksContainers.ts} (95%) diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeApi.ts index b8b8c18..02292dc 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -1,7 +1,7 @@ import { Vector3 } from 'three' import { Block, PatchKey } from '../common/types' -import { BlocksPatch } from '../data/DataContainers' +import { BlocksPatch } from '../data/BlocksContainers' import { WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index f7585ad..cf4782a 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,18 +1,21 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' -import { ChunkFactory, EntityType, PseudoRandomDistMap } from '../index' +import { ChunkFactory, EntityType, PseudoRandomDistributionMap } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' import { BlockData, BlocksContainer, BlocksPatch, -} from '../data/DataContainers' +} from '../data/BlocksContainers' import { Block, EntityData, PatchKey } from '../common/types' import { asBox2, asVect2, asVect3 } from '../common/utils' // TODO remove hardcoded entity dimensions to compute from entity type const entityDefaultDims = new Vector3(10, 20, 10) +// TODO move somewhere else +const defaultDistMap = new PseudoRandomDistributionMap() +defaultDistMap.populate() export const computePatch = (patchKey: PatchKey) => { const patch = new BlocksPatch(patchKey) @@ -95,7 +98,7 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { // query entities at current block const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) const mapPos = asVect2(blockPos) - const mapItems = PseudoRandomDistMap.instance.iterMapItems(entityShaper, mapPos) + const mapItems = defaultDistMap.iterMapItems(entityShaper, mapPos) for (const mapPos of mapItems) { const entityPos = asVect3(mapPos) const entity = genEntity(entityPos) @@ -113,7 +116,7 @@ const genEntities = (blocksContainer: BlocksContainer) => { const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) const mapBox = asBox2(blocksContainer.bbox) - const entitiesIter = PseudoRandomDistMap.instance.iterMapItems(entityShaper, mapBox) + const entitiesIter = defaultDistMap.iterMapItems(entityShaper, mapBox) for (const mapPos of entitiesIter) { // use global coords in case entity center is from adjacent patch const entityPos = asVect3(mapPos) diff --git a/src/common/types.ts b/src/common/types.ts index b38a422..bdcced0 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,6 +1,6 @@ import { Box3, Vector2, Vector3 } from 'three' -import { BlockData } from '../data/DataContainers' +import { BlockData } from '../data/BlocksContainers' import { BiomeType, BlockType } from '../procgen/Biome' import { LinkedList } from './misc' diff --git a/src/config/WorldConfig.ts b/src/config/WorldConfig.ts index 9598578..1e2c12d 100644 --- a/src/config/WorldConfig.ts +++ b/src/config/WorldConfig.ts @@ -3,4 +3,5 @@ export class WorldConfig { // max cache radius as a power of two static cachePowLimit = 2 // 4 => 16 patches radius + static defaultDistMapPeriod = 4 * this.patchSize } diff --git a/src/data/DataContainers.ts b/src/data/BlocksContainers.ts similarity index 95% rename from src/data/DataContainers.ts rename to src/data/BlocksContainers.ts index ac79103..c742932 100644 --- a/src/data/DataContainers.ts +++ b/src/data/BlocksContainers.ts @@ -1,4 +1,4 @@ -import { Box3, Vector2, Vector3 } from 'three' +import { Box2, Box3, Vector2, Vector3 } from 'three' import { Block, PatchBlock, PatchKey, WorldChunk, ChunkDataContainer, EntityData, } from '../common/types' import { @@ -194,13 +194,14 @@ export class BlocksContainer { return block } - setBlock(localPos: Vector3, blockType: BlockType) { - const blockIndex = localPos.x * this.dimensions.x + localPos.z - const block = { - level: localPos.y, - type: blockType, + setBlock(pos: Vector3, blockData: BlockData, isLocalPos = false) { + const isWithingPatch = isLocalPos ? this.isWithinLocalRange(pos) : + this.isWithinGlobalRange(pos) + if (isWithingPatch) { + const localPos = isLocalPos ? pos : this.toLocalPos(pos) + const blockIndex = this.getBlockIndex(localPos) + this.writeBlockData(blockIndex, blockData) } - this.writeBlockData(blockIndex, block) // const levelMax = blockLevel + blockData.over.length // bbox.min.y = Math.min(bbox.min.y, levelMax) // bbox.max.y = Math.max(bbox.max.y, levelMax) @@ -404,7 +405,7 @@ export class BlocksPatch extends BlocksContainer { } } -export class PatchContainer { +export class PatchMap { bbox: Box3 = new Box3() patchLookup: Record = {} @@ -420,8 +421,8 @@ export class PatchContainer { // const origin = BlocksPatch.asPatchCoords(center) const { min, max } = this.patchRange for (let { x } = min; x < max.x; x++) { - for (let { z } = min; z < max.z; z++) { - const patchKey = `${x}:${z}` + for (let { y } = min; y < max.y; y++) { + const patchKey = `${x}:${y}` const patchBox = getBboxFromPatchKey(patchKey) if (patchBboxMask(patchBox)) { this.patchLookup[patchKey] = null @@ -433,10 +434,18 @@ export class PatchContainer { get patchRange() { const rangeMin = convertPosToPatchId(this.bbox.min) const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) - const patchRange = new Box3(asVect3(rangeMin), asVect3(rangeMax)) + const patchRange = new Box2(rangeMin, rangeMax) return patchRange } + get externalBbox() { + const { min, max } = this.patchRange + min.multiplyScalar(WorldConfig.patchSize) + max.multiplyScalar(WorldConfig.patchSize) + const extBbox = new Box2(min, max) + return extBbox + } + get count() { return Object.keys(this.patchLookup).length } @@ -475,7 +484,7 @@ export class PatchContainer { }) } - compareWith(otherContainer: PatchContainer) { + compareWith(otherContainer: PatchMap) { const patchKeysDiff: Record = {} // added keys e.g. keys in current container but not found in other Object.keys(this.patchLookup) diff --git a/src/data/BoardContainer.ts b/src/data/BoardContainer.ts index af7c9db..449faaf 100644 --- a/src/data/BoardContainer.ts +++ b/src/data/BoardContainer.ts @@ -1,16 +1,24 @@ -import { Box3, Vector3 } from 'three' +import { Box2, Box3, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock } from '../common/types' -import { asVect2 } from '../common/utils' -import { BlockData, BlockMode, PatchContainer } from './DataContainers' -import { WorldCacheContainer } from '../index' +import { asBox2, asVect2, asVect3 } from '../common/utils' +import { BlockData, BlockMode, PatchMap } from './BlocksContainers' +import { BlockType, PseudoRandomDistributionMap, WorldCacheContainer, WorldConfig } from '../index' export type BoardStub = { bbox: Box3, data: BlockData } -export class BoardContainer extends PatchContainer { +const distParams = { + minDistance: 5, + maxDistance: 16, + tries: 20, +} +const distMap = new PseudoRandomDistributionMap(undefined, distParams) +distMap.populate() + +export class BoardContainer extends PatchMap { boardCenter boardRadius boardMaxHeightDiff @@ -26,7 +34,7 @@ export class BoardContainer extends PatchContainer { } restoreOriginalPatches() { - const original_patches_container = new PatchContainer() + const original_patches_container = new PatchMap() original_patches_container.initFromBoxAndMask(this.bbox) original_patches_container.populateFromExisting( WorldCacheContainer.instance.availablePatches, @@ -128,6 +136,20 @@ export class BoardContainer extends PatchContainer { genStartPosEntities() { const existingBoardEntities = this.getBoardEntities() + const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) + this.patchRange.clone().expandByScalar(WorldConfig.patchSize) + const boardMapRange = asBox2(this.bbox) + const items = distMap.iterMapItems(entityShape, boardMapRange, () => 1) + for (const mapPos of items) { + const pos = asVect3(mapPos) + const patch = this.findPatch(pos) + const block = patch?.getBlock(pos, false) + if (patch && block) { + block.data.type = BlockType.MUD + patch.writeBlockData(block.index, block.data) + // patch.setBlock(block.pos, block.data) + } + } // discard entities from spawning over existing entities const discardEntity = (entity: EntityData) => existingBoardEntities .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) diff --git a/src/data/CacheContainer.ts b/src/data/CacheContainer.ts index 48b9731..1f40bef 100644 --- a/src/data/CacheContainer.ts +++ b/src/data/CacheContainer.ts @@ -4,12 +4,12 @@ import { PatchKey } from '../common/types' import { WorldConfig } from '../config/WorldConfig' import { WorldComputeApi } from '../index' -import { BlocksPatch, PatchContainer } from './DataContainers' +import { BlocksPatch, PatchMap } from './BlocksContainers' /** * Blocks cache */ -export class CacheContainer extends PatchContainer { +export class CacheContainer extends PatchMap { // eslint-disable-next-line no-use-before-define static singleton: CacheContainer pendingRefresh = false @@ -43,7 +43,7 @@ export class CacheContainer extends PatchContainer { //, patchMask = () => true) { let changesDiff if (!this.pendingRefresh) { - const emptyContainer = new PatchContainer() + const emptyContainer = new PatchMap() emptyContainer.initFromBoxAndMask(bbox) changesDiff = emptyContainer.compareWith(CacheContainer.instance) const hasChanged = Object.keys(changesDiff).length > 0 diff --git a/src/index.ts b/src/index.ts index d99372a..f0a7b24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,13 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { PseudoRandomDistMap, RandomDistributionMap } from './procgen/DistributionMap' +export { PseudoRandomDistributionMap } from './procgen/DistributionMap' export { BlocksContainer, BlocksPatch, - PatchContainer, + PatchMap, BlockMode, -} from './data/DataContainers' +} from './data/BlocksContainers' export { BoardContainer } from './data/BoardContainer' export { Biome, BlockType } from './procgen/Biome' export { EntityType } from './common/types' diff --git a/src/procgen/DistributionMap.ts b/src/procgen/DistributionMap.ts index d5ccbe9..316c840 100644 --- a/src/procgen/DistributionMap.ts +++ b/src/procgen/DistributionMap.ts @@ -4,10 +4,14 @@ import { Box2, Vector2, Vector3 } from 'three' import { ProcLayer } from './ProcLayer' import { EntityData } from '../common/types' +import { WorldConfig } from '../config/WorldConfig' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' const probabilityThreshold = Math.pow(2, 8) +const bmin = new Vector2(0, 0) +const bmax = new Vector2(WorldConfig.defaultDistMapPeriod, WorldConfig.defaultDistMapPeriod) +const distMapDefaultBox = new Box2(bmin, bmax) const distMapDefaults = { minDistance: 8, maxDistance: 100, @@ -15,16 +19,18 @@ const distMapDefaults = { } /** - * Map for querying/iterating randomly distributed items - * at block level or from custom box range + * Infinite map using repeatable seamless pattern to provide + * independant, deterministic and approximated random distribution + * Enable querying/iterating randomly distributed items at block + * level or from custom box range */ -export class RandomDistributionMap { +export class PseudoRandomDistributionMap { bbox: Box2 params points: Vector2[] = [] densityMap = new ProcLayer('treemap') - constructor(bbox: Box2, distParams: any = {}) { + constructor(bbox: Box2 = distMapDefaultBox, distParams: any = distMapDefaults) { this.bbox = bbox this.params = distParams } @@ -46,83 +52,9 @@ export class RandomDistributionMap { this.points = p.fill() .map(point => new Vector2(point[0] as number, point[1] as number).round()) - } - - spawnProbabilityEval(pos: Vector2) { - const maxCount = 1 // 16 * Math.round(Math.exp(10)) - const val = this.densityMap?.eval(pos) - const adjustedVal = val - ? (16 * Math.round(Math.exp((1 - val) * 10))) / maxCount - : 0 - return adjustedVal - } - - hasSpawned(itemPos: Vector2) { - // eval spawn probability at entity center - const spawnProbabilty = this.spawnProbabilityEval(itemPos) - const itemId = itemPos.x + ':' + itemPos.y - const prng = alea(itemId) - const hasSpawned = prng() * spawnProbabilty < probabilityThreshold - return hasSpawned - } - - /** - * all entities belonging or overlapping with given box area - * or all entities found at given block position - * @param patchCoords - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, - pointOrRange: Vector2 | Box2, - // spawnProbabilityOverride: (entityPos?: Vector2) => number, - ) { - // return entities in patch local coords - for (const entityPos of this.points) { - const entityShape = entityShaper(entityPos) - const isWithinRange = pointOrRange instanceof Box2 ? - entityShape.intersectsBox(pointOrRange) : entityShape.containsPoint(pointOrRange) - // TODO eval entity spawn probability here - if (isWithinRange && this.hasSpawned(entityPos)) - yield entityPos - } - } - - - // /** - // * Randomly spawn entites according to custom distribution - // */ - // static spawnEntity(pos: Vector2) { - // // return Math.sin(0.01 * pos.x * pos.y) > 0.99 - // const offset = 10 - // return pos.x % 20 === offset && pos.y % 20 === offset - // } -} - -/** - * Infinite map using repeatable seamless pattern providing - * independant, deterministic and approximated random distribution - */ -export class PseudoRandomDistMap extends RandomDistributionMap { - // eslint-disable-next-line no-use-before-define - static singleton: PseudoRandomDistMap - - constructor(mapPeriod: number, params = distMapDefaults) { - super(new Box2(new Vector2(0, 0), new Vector2(mapPeriod, mapPeriod)), params) - } - - static get instance() { - return PseudoRandomDistMap.singleton - } - - static set instance(defaultInstance: PseudoRandomDistMap) { - PseudoRandomDistMap.singleton = defaultInstance - } - - override populate() { - super.populate() // make seamless repeatable map - const { dimensions } = this - const radius = this.params.minDistance / 2 + + const radius = params.minDistance / 2 const edgePoints = this.points .map(point => { const pointCopy = point.clone() @@ -142,14 +74,32 @@ export class PseudoRandomDistMap extends RandomDistributionMap { edgePoints.forEach(edgePoint => edgePoint && this.points.push(edgePoint)) } + spawnProbabilityEval(pos: Vector2) { + const maxCount = 1 // 16 * Math.round(Math.exp(10)) + const val = this.densityMap?.eval(pos) + const adjustedVal = val + ? (16 * Math.round(Math.exp((1 - val) * 10))) / maxCount + : 0 + 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 + } + /** * Either whole area or individual point overlapping with entity * @param input * @param entityMask */ - override *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, + *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, inputPointOrRange: Vector2 | Box2, - // spawnProbabilityOverride: (entityPos?: Vector2) => number, + spawnProbabilityOverride: (entityPos?: Vector2) => number, // entityMask = (_entity: EntityData) => false ) { const { dimensions } = this @@ -179,17 +129,27 @@ export class PseudoRandomDistMap extends RandomDistributionMap { for (const entityCenter of pointCandidates) { const entityPos = toRealMapPos(entityCenter) - if (this.hasSpawned(entityPos)) { + if (this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) { yield entityPos } } } + + // /** + // * Randomly spawn entites according to custom distribution + // */ + // static spawnEntity(pos: Vector2) { + // // return Math.sin(0.01 * pos.x * pos.y) > 0.99 + // const offset = 10 + // return pos.x % 20 === offset && pos.y % 20 === offset + // } } + /** * Storing entities at biome level with overlap at biomes' transitions */ -export class OverlappingEntitiesMap extends RandomDistributionMap { +export class OverlappingEntitiesMap { //extends RandomDistributionMap { // entities stored per biome static biomeMapsLookup: Record = {} // getAdjacentEntities() { diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 1d95d64..1594901 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -2,12 +2,12 @@ import { Box3, MathUtils, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock, PatchId } from '../common/types' import { asVect2, asVect3 } from '../common/utils' -import { BlockData, BlockMode } from '../data/DataContainers' +import { BlockData, BlockMode } from '../data/BlocksContainers' import { BlockType } from '../index' import { TreeGenerators } from './TreeGenerator' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND // disabled if NONE // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { From 3a3a925a66d5e71cb2d9759d1dfbae9727b5dab2 Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 29 Aug 2024 11:23:16 +0000 Subject: [PATCH 23/45] refactor: renaming, splitting, switch to Box2 wherever appropriate --- src/api/WorldComputeApi.ts | 13 +- src/api/world-compute.ts | 16 +- src/common/types.ts | 2 +- src/common/utils.ts | 52 +-- .../BlocksPatch.ts} | 330 +++--------------- .../BoardMap.ts} | 45 ++- .../CacheMap.ts} | 32 +- src/datacontainers/DataContainers.ts | 235 +++++++++++++ src/index.ts | 14 +- ...ibutionMap.ts => RandomDistributionMap.ts} | 6 +- src/tools/ChunkFactory.ts | 2 +- 11 files changed, 375 insertions(+), 372 deletions(-) rename src/{data/BlocksContainers.ts => datacontainers/BlocksPatch.ts} (56%) rename src/{data/BoardContainer.ts => datacontainers/BoardMap.ts} (78%) rename src/{data/CacheContainer.ts => datacontainers/CacheMap.ts} (84%) create mode 100644 src/datacontainers/DataContainers.ts rename src/procgen/{DistributionMap.ts => RandomDistributionMap.ts} (97%) diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeApi.ts index 02292dc..bc0d77d 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -1,8 +1,7 @@ import { Vector3 } from 'three' import { Block, PatchKey } from '../common/types' -import { BlocksPatch } from '../data/BlocksContainers' -import { WorldCompute, WorldUtils } from '../index' +import { BlocksPatchContainer, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { PatchCompute = 'computePatch', @@ -22,12 +21,12 @@ interface ComputeApiInterface { blockPosBatch: Vector3[], params?: any, ): Block[] | Promise - // computePatch(patchKey: PatchKey): BlocksPatch | Promise + // computePatch(patchKey: PatchKey): BlocksPatchContainer | Promise iterPatchCompute( patchKeysBatch: PatchKey[], ): - | Generator - | AsyncGenerator + | Generator + | AsyncGenerator } export class WorldComputeApi implements ComputeApiInterface { @@ -122,12 +121,12 @@ export class WorldComputeProxy implements ComputeApiInterface { async *iterPatchCompute(patchKeysBatch: PatchKey[]) { for (const patchKey of patchKeysBatch) { - // const emptyPatch = new BlocksPatch(patchKey) + // const emptyPatch = new BlocksPatchContainer(patchKey) const patchStub = await this.workerCall( ComputeApiCall.PatchCompute, [patchKey], // [emptyPatch.bbox] ) - const patch = BlocksPatch.fromStub(patchStub) + const patch = BlocksPatchContainer.fromStub(patchStub) yield patch } } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index cf4782a..5052b70 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,24 +1,22 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' -import { ChunkFactory, EntityType, PseudoRandomDistributionMap } from '../index' +import { ChunkFactory, EntityType, PseudoDistributionMap } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' import { - BlockData, - BlocksContainer, - BlocksPatch, -} from '../data/BlocksContainers' + BlockData, BlocksPatchContainer, +} from '../datacontainers/BlocksPatch' import { Block, EntityData, PatchKey } from '../common/types' import { asBox2, asVect2, asVect3 } from '../common/utils' // TODO remove hardcoded entity dimensions to compute from entity type const entityDefaultDims = new Vector3(10, 20, 10) // TODO move somewhere else -const defaultDistMap = new PseudoRandomDistributionMap() +const defaultDistMap = new PseudoDistributionMap() defaultDistMap.populate() export const computePatch = (patchKey: PatchKey) => { - const patch = new BlocksPatch(patchKey) + const patch = new BlocksPatchContainer(patchKey) genGroundBlocks(patch) genEntities(patch) return patch @@ -111,7 +109,7 @@ export const bakeEntities = (_entities: EntityData) => { // TODO } -const genEntities = (blocksContainer: BlocksContainer) => { +const genEntities = (blocksContainer: BlocksPatchContainer) => { // query entities on patch range const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) @@ -130,7 +128,7 @@ const genEntities = (blocksContainer: BlocksContainer) => { /** * Fill container with ground blocks */ -const genGroundBlocks = (blocksContainer: BlocksContainer) => { +const genGroundBlocks = (blocksContainer: BlocksPatchContainer) => { const { min, max } = blocksContainer.bbox // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z // const prng = alea(patchId) diff --git a/src/common/types.ts b/src/common/types.ts index bdcced0..054ca60 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,6 +1,6 @@ import { Box3, Vector2, Vector3 } from 'three' -import { BlockData } from '../data/BlocksContainers' +import { BlockData } from '../datacontainers/BlocksPatch' import { BiomeType, BlockType } from '../procgen/Biome' import { LinkedList } from './misc' diff --git a/src/common/utils.ts b/src/common/utils.ts index cd8d598..b8c0f7d 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -256,46 +256,28 @@ const parsePatchKey = (patchKey: PatchKey) => { return patchId } -const convertPosToPatchId = ( - position: Vector3, - patchSize: number = WorldConfig.patchSize, +const patchIdFromPos = ( + position: Vector2, + patchSize: Vector2, ) => { - const orig_x = Math.floor(position.x / patchSize) - const orig_z = Math.floor(position.z / patchSize) - const patchCoords = new Vector2(orig_x, orig_z) - return patchCoords + const patchId = position.clone().divide(patchSize).floor() + return patchId } -const computePatchKey = ( - patchId: Box3 | Vector3 | Vector2, - patchSize: number = WorldConfig.patchSize, -) => { - const inputCopy: Vector3 | Box3 = - patchId instanceof Vector2 - ? new Vector3(patchId.x, 0, patchId.y) - : patchId.clone() - const point = - inputCopy instanceof Box3 - ? (inputCopy as Box3).getCenter(new Vector3()) - : (inputCopy as Vector3).clone() - - const patchOrigin = convertPosToPatchId(point, patchSize) - const { x, y } = patchOrigin +const serializePatchId = (patchId: PatchId) => { + const { x, y } = patchId const patchKey = `${x}:${y}` return patchKey } -const getBboxFromPatchKey = ( +const patchBoxFromKey = ( patchKey: string, - patchSize: number = WorldConfig.patchSize, + patchDims: Vector2, ) => { const patchCoords = parsePatchKey(patchKey) - const bmin = asVect3(patchCoords.clone().multiplyScalar(patchSize)) - const bmax = asVect3( - patchCoords.clone().addScalar(1).multiplyScalar(patchSize), - ) - bmax.y = 512 - const bbox = new Box3(bmin, bmax) + const bmin = patchCoords.clone().multiply(patchDims) + const bmax = patchCoords.clone().addScalar(1).multiply(patchDims) + const bbox = new Box2(bmin, bmax) return bbox } @@ -321,7 +303,7 @@ function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { return chunk_ids } -const getBboxFromChunkId = ( +const chunkBoxFromId = ( chunkId: ChunkId, patchSize: number = WorldConfig.patchSize, ) => { @@ -351,11 +333,11 @@ export { asBox2, asBox3, parsePatchKey, - convertPosToPatchId, - computePatchKey, - getBboxFromPatchKey, + patchIdFromPos, + serializePatchId, + patchBoxFromKey, parseChunkKey, serializeChunkId, - getBboxFromChunkId, + chunkBoxFromId, genChunkIds, } diff --git a/src/data/BlocksContainers.ts b/src/datacontainers/BlocksPatch.ts similarity index 56% rename from src/data/BlocksContainers.ts rename to src/datacontainers/BlocksPatch.ts index c742932..3170495 100644 --- a/src/data/BlocksContainers.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -1,19 +1,21 @@ -import { Box2, Box3, Vector2, Vector3 } from 'three' +import { Box3, Vector2, Vector3 } from 'three' -import { Block, PatchBlock, PatchKey, WorldChunk, ChunkDataContainer, EntityData, } from '../common/types' +import { Block, PatchBlock, WorldChunk, ChunkDataContainer, EntityData, } from '../common/types' import { - asVect3, - computePatchKey, - convertPosToPatchId, - getBboxFromChunkId, - getBboxFromPatchKey, + patchBoxFromKey, parsePatchKey, parseThreeStub, serializeChunkId, + asVect2, + asBox3, + chunkBoxFromId, + patchIdFromPos, + serializePatchId, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' +import { PatchDataContainer } from './DataContainers' export enum BlockMode { DEFAULT, @@ -43,11 +45,13 @@ const BlockDataBitAllocation = { export type BlockIteratorRes = IteratorResult +const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) + /** * GenericBlocksContainer * multi purpose blocks container */ -export class BlocksContainer { +export class BlocksPatchContainer implements PatchDataContainer { bbox: Box3 dimensions = new Vector3() margin = 0 @@ -55,8 +59,14 @@ export class BlocksContainer { rawDataContainer: Uint32Array entities: EntityData[] = [] - constructor(bbox: Box3, margin = 1) { - this.bbox = bbox.clone() + key: string | null + id: Vector2 | null + + constructor(patchBoxOrKey: Box3 | string, margin = 1) { + this.bbox = patchBoxOrKey instanceof Box3 ? patchBoxOrKey.clone() : + asBox3(patchBoxFromKey(patchBoxOrKey, getDefaultPatchDim())) + this.key = patchBoxOrKey instanceof Box3 ? null : patchBoxOrKey + this.id = this.key ? parsePatchKey(this.key) : null this.bbox.getSize(this.dimensions) this.margin = margin const { extendedDims } = this @@ -64,9 +74,18 @@ export class BlocksContainer { } duplicate() { - const copy = new BlocksContainer(this.bbox) - this.rawDataContainer.forEach((v, i) => (copy.rawDataContainer[i] = v)) - // copy.entitiesChunks = this.entitiesChunks + const copy = new BlocksPatchContainer(this.key || this.bbox)// new BlocksPatchContainer(this.bbox) + this.rawDataContainer.forEach( + (rawVal, i) => (copy.rawDataContainer[i] = rawVal), + ) + copy.entities = this.entities + .map(entity => { + const entityCopy: EntityData = { + ...entity, + bbox: entity.bbox.clone(), + } + return entityCopy + }) return copy } @@ -323,56 +342,31 @@ export class BlocksContainer { return chunk } - static fromStub(stub: any) { - const { rawDataContainer, entities, bbox } = stub - const blocksContainer = new BlocksContainer(parseThreeStub(bbox)) - blocksContainer.rawDataContainer = rawDataContainer - blocksContainer.entities = entities - .map((stub: EntityData) => ({ - ...stub, - bbox: parseThreeStub(stub.bbox), - })) - // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => - // patch.entitiesChunks.push(entityChunk), - // ) - return blocksContainer - } -} - -/** - * Patch - */ -export class BlocksPatch extends BlocksContainer { - id: Vector2 - key: string - - constructor(patchKey: string) { - super(getBboxFromPatchKey(patchKey)) // .expandByScalar(1)) - this.key = patchKey - this.id = parsePatchKey(patchKey) + get chunkIds() { + return this.id ? ChunkFactory.default.genChunksIdsFromPatchId(this.id) : [] } - override duplicate() { - const copy = new BlocksPatch(this.key) - this.rawDataContainer.forEach( - (rawVal, i) => (copy.rawDataContainer[i] = rawVal), - ) - copy.entities = this.entities - .map(entity => { - const entityCopy: EntityData = { - ...entity, - bbox: entity.bbox.clone(), - } - return entityCopy - }) - return copy + toChunks() { + const chunks = this.chunkIds.map(chunkId => { + const chunkBox = chunkBoxFromId(chunkId, WorldConfig.patchSize) + const chunk = this.toChunk(chunkBox) + const worldChunk: WorldChunk = { + key: serializeChunkId(chunkId), + data: chunk.data, + } + return worldChunk + }) + return chunks } - static override fromStub(patchStub: any) { + static fromStub(patchStub: any) { const { rawDataContainer, entities } = patchStub - const bbox = parseThreeStub(patchStub.bbox) - const patchKey = patchStub.key || computePatchKey(bbox) - const patch = new BlocksPatch(patchKey) + const bbox = parseThreeStub(patchStub.bbox) as Box3 + const patchCenter = asVect2(bbox.getCenter(new Vector3)) + const patchDim = asVect2(bbox.getSize(new Vector3()).round()) + const patchId = patchIdFromPos(patchCenter, patchDim) + const patchKey = patchStub.key || serializePatchId(patchId) + const patch = new BlocksPatchContainer(patchKey) patch.rawDataContainer = rawDataContainer patch.entities = entities .map((stub: EntityData) => ({ @@ -386,224 +380,4 @@ export class BlocksPatch extends BlocksContainer { // ) return patch } - - get chunkIds() { - return ChunkFactory.default.genChunksIdsFromPatchId(this.id) - } - - toChunks() { - const chunks = this.chunkIds.map(chunkId => { - const chunkBox = getBboxFromChunkId(chunkId, WorldConfig.patchSize) - const chunk = super.toChunk(chunkBox) - const worldChunk: WorldChunk = { - key: serializeChunkId(chunkId), - data: chunk.data, - } - return worldChunk - }) - return chunks - } -} - -export class PatchMap { - bbox: Box3 = new Box3() - patchLookup: Record = {} - - initFromBoxAndMask( - bbox: Box3, - patchBboxMask = (patchBbox: Box3) => patchBbox, - ) { - this.bbox = bbox - this.patchLookup = {} - // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) - // const range = BlocksPatch.asPatchCoords(halfDimensions) - // const center = this.bbox.getCenter(new Vector3()) - // const origin = BlocksPatch.asPatchCoords(center) - const { min, max } = this.patchRange - for (let { x } = min; x < max.x; x++) { - for (let { y } = min; y < max.y; y++) { - const patchKey = `${x}:${y}` - const patchBox = getBboxFromPatchKey(patchKey) - if (patchBboxMask(patchBox)) { - this.patchLookup[patchKey] = null - } - } - } - } - - get patchRange() { - const rangeMin = convertPosToPatchId(this.bbox.min) - const rangeMax = convertPosToPatchId(this.bbox.max).addScalar(1) - const patchRange = new Box2(rangeMin, rangeMax) - return patchRange - } - - get externalBbox() { - const { min, max } = this.patchRange - min.multiplyScalar(WorldConfig.patchSize) - max.multiplyScalar(WorldConfig.patchSize) - const extBbox = new Box2(min, max) - return extBbox - } - - get count() { - return Object.keys(this.patchLookup).length - } - - get patchKeys() { - return Object.keys(this.patchLookup) - } - - get chunkIds() { - return this.availablePatches.map(patch => patch.chunkIds).flat() - } - - get availablePatches() { - return Object.values(this.patchLookup).filter(val => val) as BlocksPatch[] - } - - get missingPatchKeys() { - return Object.keys(this.patchLookup).filter( - key => !this.patchLookup[key], - ) as PatchKey[] - } - - // autoFill(fillingVal=0){ - // this.patchKeys.forEach(key=>this.patchLookup[key] = new BlocksPatch(key)) - // this.availablePatches.forEach(patch=>patch.iterOverBlocks) - // } - - populateFromExisting(patches: BlocksPatch[], cloneObjects = false) { - const { min, max } = this.bbox - patches - .filter(patch => this.patchLookup[patch.key] !== undefined) - .forEach(patch => { - this.patchLookup[patch.key] = cloneObjects ? patch.duplicate() : patch - min.y = Math.min(patch.bbox.min.y, min.y) - max.y = Math.max(patch.bbox.max.y, max.y) - }) - } - - compareWith(otherContainer: PatchMap) { - const patchKeysDiff: Record = {} - // added keys e.g. keys in current container but not found in other - Object.keys(this.patchLookup) - .filter(patchKey => otherContainer.patchLookup[patchKey] === undefined) - .forEach(patchKey => (patchKeysDiff[patchKey] = true)) - // missing keys e.g. found in other container but not in current - Object.keys(otherContainer.patchLookup) - .filter(patchKey => this.patchLookup[patchKey] === undefined) - .forEach(patchKey => (patchKeysDiff[patchKey] = false)) - return patchKeysDiff - } - - toChunks() { - const exportedChunks = this.availablePatches - .map(patch => patch.toChunks()) - .flat() - return exportedChunks - } - - findPatch(blockPos: Vector3) { - // const point = new Vector3( - // inputPoint.x, - // 0, - // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, - // ) - - const res = this.availablePatches.find(patch => - patch.containsBlock(blockPos), - ) - return res - } - - getBlock(blockPos: Vector3) { - return this.findPatch(blockPos)?.getBlock(blockPos, false) - } - - getAllPatchesEntities(skipDuplicate = true) { - const entities: EntityData[] = [] - for (const patch of this.availablePatches) { - patch.entities.forEach(entity => { - if (!skipDuplicate || !entities.find(ent => ent.bbox.equals(entity.bbox))) { - entities.push(entity) - } - }) - } - return entities - } - - // *iterAllPatchesBlocks() { - // for (const patch of this.availablePatches) { - // const blocks = patch.iterOverBlocks(undefined, false, false) - // for (const block of blocks) { - // yield block - // } - // } - // } - - // getMergedRows(zRowIndex: number) { - // const sortedPatchesRows = this.availablePatches - // .filter( - // patch => zRowIndex >= patch.bbox.min.z && zRowIndex <= patch.bbox.min.z, - // ) - // .sort((p1, p2) => p1.bbox.min.x - p2.bbox.min.x) - // .map(patch => patch.getBlocksRow(zRowIndex)) - // const mergedRows = sortedPatchesRows.reduce((arr1, arr2) => { - // const mergedArray = new Uint32Array(arr1.length + arr2.length) - // mergedArray.set(arr1) - // mergedArray.set(arr2, arr1.length) - // return mergedArray - // }) - // return mergedRows - // } - - // iterMergedRows() { - // const { min, max } = this.patchRange - // for (let zPatchIndex = min.z; zPatchIndex <= max.z; zPatchIndex++) { - // for (let zRowIndex = min.z; zRowIndex < max.z; zRowIndex++) {} - // } - // } - - // getMergedCols(xColIndex: number) { - - // } - - // mergedLinesIteration() { - // const { min, max } = this.bbox - // for (let x = min.x; x < max.x; x++) { - // for (let z = min.z; z < max.z; z++) { - - // } - // } - // } - - // toMergedContainer() { - // const mergedBox = this.availablePatches.map(patch => patch.bbox) - // .reduce((merge, bbox) => merge.union(bbox), new Box3()) - // // const mergedContainer = - // } - - // static fromMergedContainer() { - - // } - // mergeBlocks(blocksContainer: BlocksContainer) { - // // // for each patch override with blocks from blocks container - // this.availablePatches.forEach(patch => { - // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) - // for (const target_block of blocksIter) { - // const source_block = blocksContainer.getBlock(target_block.pos, false) - // if (source_block && source_block.pos.y > 0 && target_block.index) { - // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE - // block_type = - // source_block.type === BlockType.TREE_TRUNK - // ? BlockType.TREE_TRUNK - // : block_type - // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y - // patch.writeBlock(target_block.index, block_level, block_type) - // // console.log(source_block?.pos.y) - // } - // } - // }) - // } } diff --git a/src/data/BoardContainer.ts b/src/datacontainers/BoardMap.ts similarity index 78% rename from src/data/BoardContainer.ts rename to src/datacontainers/BoardMap.ts index 449faaf..e1149d5 100644 --- a/src/data/BoardContainer.ts +++ b/src/datacontainers/BoardMap.ts @@ -1,40 +1,44 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock } from '../common/types' -import { asBox2, asVect2, asVect3 } from '../common/utils' -import { BlockData, BlockMode, PatchMap } from './BlocksContainers' -import { BlockType, PseudoRandomDistributionMap, WorldCacheContainer, WorldConfig } from '../index' +import { asVect2, asVect3 } from '../common/utils' +import { BlockType, WorldCacheContainer, WorldConfig } from '../index' +import { PseudoDistributionMap } from '../procgen/RandomDistributionMap' +import { BlockData, BlockMode, BlocksPatchContainer } from './BlocksPatch' +import { PatchesMap } from './DataContainers' export type BoardStub = { bbox: Box3, data: BlockData } +const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) + const distParams = { minDistance: 5, maxDistance: 16, tries: 20, } -const distMap = new PseudoRandomDistributionMap(undefined, distParams) +const distMap = new PseudoDistributionMap(undefined, distParams) distMap.populate() -export class BoardContainer extends PatchMap { +export class BoardContainer extends PatchesMap { boardCenter boardRadius boardMaxHeightDiff constructor(center: Vector3, radius: number, maxHeightDiff: number) { - super() + super(getDefaultPatchDim()) this.boardRadius = radius this.boardCenter = center.clone().floor() this.boardMaxHeightDiff = maxHeightDiff - const board_dims = new Vector3(radius, 0, radius).multiplyScalar(2) - this.bbox.setFromCenterAndSize(this.boardCenter, board_dims) + const board_dims = new Vector2(radius, radius).multiplyScalar(2) + this.bbox.setFromCenterAndSize(asVect2(this.boardCenter), board_dims) this.initFromBoxAndMask(this.bbox) } restoreOriginalPatches() { - const original_patches_container = new PatchMap() + const original_patches_container = new PatchesMap(getDefaultPatchDim()) original_patches_container.initFromBoxAndMask(this.bbox) original_patches_container.populateFromExisting( WorldCacheContainer.instance.availablePatches, @@ -64,8 +68,8 @@ export class BoardContainer extends PatchMap { // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) // reset bbox to refine bounds - this.bbox.min = this.boardCenter.clone() - this.bbox.max = this.boardCenter.clone() + this.bbox.min = asVect2(this.boardCenter) + this.bbox.max = asVect2(this.boardCenter) for (const patch of this.availablePatches) { const blocks = patch.iterOverBlocks(undefined, false) @@ -75,13 +79,25 @@ export class BoardContainer extends PatchMap { if (this.filterBoardBlocks(block.pos)) { const boardBlock = this.overrideBlock(block) patch.writeBlockData(boardBlock.index, boardBlock.data) - this.bbox.expandByPoint(boardBlock.pos) + this.bbox.expandByPoint(asVect2(boardBlock.pos)) // yield boardBlock } } } } + getAllPatchesEntities(skipDuplicate = true) { + const entities: EntityData[] = [] + for (const patch of this.availablePatches) { + patch.entities.forEach(entity => { + if (!skipDuplicate || !entities.find(ent => ent.bbox.equals(entity.bbox))) { + entities.push(entity) + } + }) + } + return entities + } + getBoardEntities() { const boardEntities = this.getAllPatchesEntities() .filter(ent => { @@ -95,7 +111,7 @@ export class BoardContainer extends PatchMap { this.availablePatches.forEach(patch => { patch.entities.forEach(entity => { const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = this.getBlock(entityCenter) + const entityCenterBlock = this.findPatch(entityCenter)?.getBlock(entityCenter, false) entityCenter.y = entity.bbox.min.y const isEntityOverlappingBoard = () => { const entityBlocks = patch.iterOverBlocks(entity.bbox) @@ -138,8 +154,7 @@ export class BoardContainer extends PatchMap { const existingBoardEntities = this.getBoardEntities() const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) this.patchRange.clone().expandByScalar(WorldConfig.patchSize) - const boardMapRange = asBox2(this.bbox) - const items = distMap.iterMapItems(entityShape, boardMapRange, () => 1) + const items = distMap.iterMapItems(entityShape, this.bbox, () => 1) for (const mapPos of items) { const pos = asVect3(mapPos) const patch = this.findPatch(pos) diff --git a/src/data/CacheContainer.ts b/src/datacontainers/CacheMap.ts similarity index 84% rename from src/data/CacheContainer.ts rename to src/datacontainers/CacheMap.ts index 1f40bef..d36c3eb 100644 --- a/src/data/CacheContainer.ts +++ b/src/datacontainers/CacheMap.ts @@ -1,24 +1,26 @@ -import { Box3, Vector3 } from 'three' +import { Box2, Box3, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' import { WorldConfig } from '../config/WorldConfig' -import { WorldComputeApi } from '../index' +import { BlocksPatchContainer, WorldComputeApi } from '../index' +import { PatchesMap } from './DataContainers' -import { BlocksPatch, PatchMap } from './BlocksContainers' +const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) /** * Blocks cache */ -export class CacheContainer extends PatchMap { +export class CacheContainer extends PatchesMap { // eslint-disable-next-line no-use-before-define + static cachePowRadius = 2 + static cacheSize = WorldConfig.patchSize * 5 static singleton: CacheContainer pendingRefresh = false builtInCache = false // specify whether cache is managed internally or separately - static cachePowRadius = 2 - static cacheSize = WorldConfig.patchSize * 5 + static get instance() { - this.singleton = this.singleton || new CacheContainer() + this.singleton = this.singleton || new CacheContainer(getDefaultPatchDim()) return this.singleton } @@ -27,8 +29,10 @@ export class CacheContainer extends PatchMap { const batchIter = WorldComputeApi.instance.iterPatchCompute(batch) // populate cache without blocking execution for await (const patch of batchIter) { - this.patchLookup[patch.key] = patch - this.bbox.union(patch.bbox) + if (patch.key) { + this.patchLookup[patch.key] = patch + this.bbox.union(patch.bbox) + } } this.pendingRefresh = false } @@ -39,11 +43,11 @@ export class CacheContainer extends PatchMap { * @param dryRun * @returns true if cache was update, false otherwise */ - async refresh(bbox: Box3) { + async refresh(bbox: Box2) { //, patchMask = () => true) { let changesDiff if (!this.pendingRefresh) { - const emptyContainer = new PatchMap() + const emptyContainer = new PatchesMap(this.patchDimensions) emptyContainer.initFromBoxAndMask(bbox) changesDiff = emptyContainer.compareWith(CacheContainer.instance) const hasChanged = Object.keys(changesDiff).length > 0 @@ -73,7 +77,7 @@ export class CacheContainer extends PatchMap { return res } - getNearPatches(patch: BlocksPatch) { + getNearPatches(patch: BlocksPatchContainer) { const dim = patch.dimensions const patchCenter = patch.bbox.getCenter(new Vector3()) const minX = patchCenter.clone().add(new Vector3(-dim.x, 0, 0)) @@ -94,9 +98,9 @@ export class CacheContainer extends PatchMap { maxXminZ, maxXmaxZ, ] - const patchNeighbours: BlocksPatch[] = neighboursCenters + const patchNeighbours: BlocksPatchContainer[] = neighboursCenters .map(patchCenter => this.findPatch(patchCenter)) - .filter(patch => patch) as BlocksPatch[] + .filter(patch => patch) as BlocksPatchContainer[] return patchNeighbours } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts new file mode 100644 index 0000000..893727f --- /dev/null +++ b/src/datacontainers/DataContainers.ts @@ -0,0 +1,235 @@ +import { Box2, Vector3, Vector2 } from "three" +import { PatchKey } from "../common/types" +import { patchIdFromPos } from "../common/utils" +import { WorldConfig } from "../config/WorldConfig" + +/** + * Generic patch data container + */ +// GenericPatch +export interface PatchDataContainer { + key: any + bbox: any + chunkIds: any + duplicate(): PatchDataContainer | null + toChunks(): any +} +/** + * Map from patch aggregation + */ +export class PatchesMap { + bbox: Box2 = new Box2() + patchDimensions: Vector2 + patchLookup: Record = {} + + constructor(patchDim: Vector2) { + this.patchDimensions = patchDim + } + + initFromBoxAndMask( + bbox: Box2, + // patchDim: Vector2, + // patchBboxFilter = (patchBbox: Box3) => patchBbox, + ) { + this.bbox = bbox + // this.patchDimensions = patchDim + this.patchLookup = {} + // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) + // const range = BlocksPatch.asPatchCoords(halfDimensions) + // const center = this.bbox.getCenter(new Vector3()) + // const origin = BlocksPatch.asPatchCoords(center) + const { min, max } = this.patchRange + for (let { x } = min; x < max.x; x++) { + for (let { y } = min; y < max.y; y++) { + const patchKey = `${x}:${y}` + // const patchBox = patchBoxFromKey(patchKey, patchDim) + // if (patchBboxFilter(patchBox)) { + this.patchLookup[patchKey] = null + // } + } + } + } + + get patchRange() { + const rangeMin = patchIdFromPos(this.bbox.min, this.patchDimensions) + const rangeMax = patchIdFromPos(this.bbox.max, this.patchDimensions).addScalar(1) + const patchRange = new Box2(rangeMin, rangeMax) + return patchRange + } + + get externalBbox() { + const { min, max } = this.patchRange + min.multiplyScalar(WorldConfig.patchSize) + max.multiplyScalar(WorldConfig.patchSize) + const extBbox = new Box2(min, max) + return extBbox + } + + get count() { + return Object.keys(this.patchLookup).length + } + + get patchKeys() { + return Object.keys(this.patchLookup) + } + + get chunkIds() { + return this.availablePatches.map(patch => patch.chunkIds).flat() + } + + get availablePatches() { + return Object.values(this.patchLookup).filter(val => val) as PatchType[] + } + + get missingPatchKeys() { + return Object.keys(this.patchLookup).filter( + key => !this.patchLookup[key], + ) as PatchKey[] + } + + // autoFill(fillingVal=0){ + // this.patchKeys.forEach(key=>this.patchLookup[key] = new BlocksPatch(key)) + // this.availablePatches.forEach(patch=>patch.iterOverBlocks) + // } + + populateFromExisting(patches: PatchType[], cloneObjects = false) { + // const { min, max } = this.bbox + patches + .filter(patch => this.patchLookup[patch.key] !== undefined) + .forEach(patch => { + this.patchLookup[patch.key] = cloneObjects ? patch.duplicate() : patch + // min.y = Math.min(patch.bbox.min.y, min.y) + // max.y = Math.max(patch.bbox.max.y, max.y) + }) + } + + compareWith(otherContainer: PatchesMap) { + const patchKeysDiff: Record = {} + // added keys e.g. keys in current container but not found in other + Object.keys(this.patchLookup) + .filter(patchKey => otherContainer.patchLookup[patchKey] === undefined) + .forEach(patchKey => (patchKeysDiff[patchKey] = true)) + // missing keys e.g. found in other container but not in current + Object.keys(otherContainer.patchLookup) + .filter(patchKey => this.patchLookup[patchKey] === undefined) + .forEach(patchKey => (patchKeysDiff[patchKey] = false)) + return patchKeysDiff + } + + toChunks() { + const exportedChunks = this.availablePatches + .map(patch => patch.toChunks()) + .flat() + return exportedChunks + } + + findPatch(blockPos: Vector3) { + // const point = new Vector3( + // inputPoint.x, + // 0, + // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, + // ) + + const res = this.availablePatches.find(patch => + patch.containsBlock(blockPos), + ) + return res + } + + // getBlock(blockPos: Vector3) { + // return this.findPatch(blockPos)?.getBlock(blockPos, false) + // } + + // getAllPatchesEntities(skipDuplicate = true) { + // const entities: EntityData[] = [] + // for (const patch of this.availablePatches) { + // patch.entities.forEach(entity => { + // if (!skipDuplicate || !entities.find(ent => ent.bbox.equals(entity.bbox))) { + // entities.push(entity) + // } + // }) + // } + // return entities + // } + + // *iterAllPatchesBlocks() { + // for (const patch of this.availablePatches) { + // const blocks = patch.iterOverBlocks(undefined, false, false) + // for (const block of blocks) { + // yield block + // } + // } + // } + + // getMergedRows(zRowIndex: number) { + // const sortedPatchesRows = this.availablePatches + // .filter( + // patch => zRowIndex >= patch.bbox.min.z && zRowIndex <= patch.bbox.min.z, + // ) + // .sort((p1, p2) => p1.bbox.min.x - p2.bbox.min.x) + // .map(patch => patch.getBlocksRow(zRowIndex)) + // const mergedRows = sortedPatchesRows.reduce((arr1, arr2) => { + // const mergedArray = new Uint32Array(arr1.length + arr2.length) + // mergedArray.set(arr1) + // mergedArray.set(arr2, arr1.length) + // return mergedArray + // }) + // return mergedRows + // } + + // iterMergedRows() { + // const { min, max } = this.patchRange + // for (let zPatchIndex = min.z; zPatchIndex <= max.z; zPatchIndex++) { + // for (let zRowIndex = min.z; zRowIndex < max.z; zRowIndex++) {} + // } + // } + + // getMergedCols(xColIndex: number) { + + // } + + // mergedLinesIteration() { + // const { min, max } = this.bbox + // for (let x = min.x; x < max.x; x++) { + // for (let z = min.z; z < max.z; z++) { + + // } + // } + // } + + // toMergedContainer() { + // const mergedBox = this.availablePatches.map(patch => patch.bbox) + // .reduce((merge, bbox) => merge.union(bbox), new Box3()) + // // const mergedContainer = + // } + + // static fromMergedContainer() { + + // } + // mergeBlocks(blocksContainer: BlocksContainer) { + // // // for each patch override with blocks from blocks container + // this.availablePatches.forEach(patch => { + // const blocksIter = patch.iterOverBlocks(blocksContainer.bbox) + // for (const target_block of blocksIter) { + // const source_block = blocksContainer.getBlock(target_block.pos, false) + // if (source_block && source_block.pos.y > 0 && target_block.index) { + // let block_type = source_block.type ? BlockType.SAND : BlockType.NONE + // block_type = + // source_block.type === BlockType.TREE_TRUNK + // ? BlockType.TREE_TRUNK + // : block_type + // const block_level = blocksContainer.bbox.min.y // source_block?.pos.y + // patch.writeBlock(target_block.index, block_level, block_type) + // // console.log(source_block?.pos.y) + // } + // } + // }) + // } +} + +/** + * Repeat patch pattern indefinitely to provide infinite map + */ +export class PatchRepeatMap extends PatchesMap { + +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index f0a7b24..c7a7796 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,17 +2,13 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { PseudoRandomDistributionMap } from './procgen/DistributionMap' -export { - BlocksContainer, - BlocksPatch, - PatchMap, - BlockMode, -} from './data/BlocksContainers' -export { BoardContainer } from './data/BoardContainer' +export { PseudoDistributionMap } from './procgen/RandomDistributionMap' +export { BoardContainer } from './datacontainers/BoardMap' export { Biome, BlockType } from './procgen/Biome' export { EntityType } from './common/types' -export { CacheContainer as WorldCacheContainer } from './data/CacheContainer' +export { PatchesMap } from './datacontainers/DataContainers' +export { BlockMode, BlocksPatchContainer } from './datacontainers/BlocksPatch' +export { CacheContainer as WorldCacheContainer } from './datacontainers/CacheMap' export { ChunkFactory } from './tools/ChunkFactory' export { WorldConfig } from './config/WorldConfig' export { WorldComputeApi } from './api/WorldComputeApi' diff --git a/src/procgen/DistributionMap.ts b/src/procgen/RandomDistributionMap.ts similarity index 97% rename from src/procgen/DistributionMap.ts rename to src/procgen/RandomDistributionMap.ts index 316c840..8079fdc 100644 --- a/src/procgen/DistributionMap.ts +++ b/src/procgen/RandomDistributionMap.ts @@ -1,6 +1,6 @@ import PoissonDiskSampling from 'poisson-disk-sampling' import alea from 'alea' -import { Box2, Vector2, Vector3 } from 'three' +import { Box2, Vector2 } from 'three' import { ProcLayer } from './ProcLayer' import { EntityData } from '../common/types' @@ -24,7 +24,7 @@ const distMapDefaults = { * Enable querying/iterating randomly distributed items at block * level or from custom box range */ -export class PseudoRandomDistributionMap { +export class PseudoDistributionMap { bbox: Box2 params points: Vector2[] = [] @@ -99,7 +99,7 @@ export class PseudoRandomDistributionMap { */ *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, inputPointOrRange: Vector2 | Box2, - spawnProbabilityOverride: (entityPos?: Vector2) => number, + spawnProbabilityOverride?: (entityPos?: Vector2) => number, // entityMask = (_entity: EntityData) => false ) { const { dimensions } = this diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 1594901..16fcebc 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -2,7 +2,7 @@ import { Box3, MathUtils, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock, PatchId } from '../common/types' import { asVect2, asVect3 } from '../common/utils' -import { BlockData, BlockMode } from '../data/BlocksContainers' +import { BlockData, BlockMode } from '../datacontainers/BlocksPatch' import { BlockType } from '../index' import { TreeGenerators } from './TreeGenerator' From 3c17d9daecaa75556f623dc3b789e4e49259de6d Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 29 Aug 2024 23:00:09 +0000 Subject: [PATCH 24/45] feat: board start positions random distribution (including support for any map input size) --- src/api/world-compute.ts | 47 ++++++++--------- src/datacontainers/BoardMap.ts | 78 ++++++++++++++-------------- src/procgen/RandomDistributionMap.ts | 61 ++++++++++------------ src/tools/ChunkFactory.ts | 2 +- 4 files changed, 89 insertions(+), 99 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 5052b70..51f38ad 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -96,9 +96,9 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { // query entities at current block const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) const mapPos = asVect2(blockPos) - const mapItems = defaultDistMap.iterMapItems(entityShaper, mapPos) - for (const mapPos of mapItems) { - const entityPos = asVect3(mapPos) + const spawnLocs = defaultDistMap.getSpawnLocations(entityShaper, mapPos) + for (const loc of spawnLocs) { + const entityPos = asVect3(loc) const entity = genEntity(entityPos) blocksBuffer = entity ? ChunkFactory.chunkifyEntity(entity, blockPos).data : blocksBuffer } @@ -109,48 +109,45 @@ export const bakeEntities = (_entities: EntityData) => { // TODO } -const genEntities = (blocksContainer: BlocksPatchContainer) => { +const genEntities = (blocksPatch: BlocksPatchContainer) => { // query entities on patch range const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) - const mapBox = asBox2(blocksContainer.bbox) - const entitiesIter = defaultDistMap.iterMapItems(entityShaper, mapBox) - for (const mapPos of entitiesIter) { - // use global coords in case entity center is from adjacent patch - const entityPos = asVect3(mapPos) - const entity = genEntity(entityPos) - if (entity) { - blocksContainer.entities.push(entity) - } - } + const mapBox = asBox2(blocksPatch.bbox) + const spawnLocs = defaultDistMap.getSpawnLocations(entityShaper, mapBox) + const spawnedEntities = spawnLocs + .map(loc => asVect3(loc)) + .map(entityPos => genEntity(entityPos)) + .filter(val => val) as EntityData[] + blocksPatch.entities = spawnedEntities } /** * Fill container with ground blocks */ -const genGroundBlocks = (blocksContainer: BlocksPatchContainer) => { - const { min, max } = blocksContainer.bbox +const genGroundBlocks = (blocksPatch: BlocksPatchContainer) => { + const { min, max } = blocksPatch.bbox // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const blocksPatchIter = blocksContainer.iterOverBlocks(undefined, false,) + const patchBlocks = blocksPatch.iterOverBlocks(undefined, false,) min.y = 512 max.y = 0 let blockIndex = 0 - for (const block of blocksPatchIter) { + for (const block of patchBlocks) { // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) const blockData = computeGroundBlock(block.pos) min.y = Math.min(min.y, blockData.level) max.y = Math.max(max.y, blockData.level) - blocksContainer.writeBlockData(blockIndex, blockData) + blocksPatch.writeBlockData(blockIndex, blockData) blockIndex++ } - blocksContainer.bbox.min = min - blocksContainer.bbox.max = max - blocksContainer.bbox.getSize(blocksContainer.dimensions) - // PatchBlocksCache.bbox.union(blocksContainer.bbox) + blocksPatch.bbox.min = min + blocksPatch.bbox.max = max + blocksPatch.bbox.getSize(blocksPatch.dimensions) + // PatchBlocksCache.bbox.union(blocksPatch.bbox) - // blocksContainer.state = PatchState.Filled - return blocksContainer + // blocksPatch.state = PatchState.Filled + return blocksPatch } diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index e1149d5..53fd0e3 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -15,13 +15,15 @@ export type BoardStub = { const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) const distParams = { - minDistance: 5, + minDistance: 10, maxDistance: 16, tries: 20, } const distMap = new PseudoDistributionMap(undefined, distParams) distMap.populate() +const DBG_ENTITIES_HIGHLIGHT_COLOR = BlockType.SNOW// NONE to disable debugging + export class BoardContainer extends PatchesMap { boardCenter boardRadius @@ -47,7 +49,7 @@ export class BoardContainer extends PatchesMap { return original_patches_container } - filterBoardBlocks(blockPos: Vector3) { + isWithinBoard(blockPos: Vector3) { let isInsideBoard = false if (blockPos) { const heightDiff = Math.abs(blockPos.y - this.boardCenter.y) @@ -76,7 +78,7 @@ export class BoardContainer extends PatchesMap { // const blocks = this.iterPatchesBlocks() for (const block of blocks) { // discard blocs not included in board shape - if (this.filterBoardBlocks(block.pos)) { + if (this.isWithinBoard(block.pos)) { const boardBlock = this.overrideBlock(block) patch.writeBlockData(boardBlock.index, boardBlock.data) this.bbox.expandByPoint(asVect2(boardBlock.pos)) @@ -102,11 +104,40 @@ export class BoardContainer extends PatchesMap { const boardEntities = this.getAllPatchesEntities() .filter(ent => { const entityCenter = ent.bbox.getCenter(new Vector3()) - return this.filterBoardBlocks(entityCenter) + return this.isWithinBoard(entityCenter) }) return boardEntities } + genStartPositions() { + const existingBoardEntities = this.getBoardEntities() + const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) + this.patchRange.clone().expandByScalar(WorldConfig.patchSize) + const spawnLocs = distMap.getSpawnLocations(entityShape, this.bbox, () => 1) + const startBlockPositions = spawnLocs + .map(loc => { + const startPos = asVect3(loc) + const patch = this.findPatch(startPos) + const block = patch?.getBlock(startPos, false) + return block + }) + .filter(startBlock => startBlock && this.isWithinBoard(startBlock.pos)) as PatchBlock[] + DBG_ENTITIES_HIGHLIGHT_COLOR && startBlockPositions.forEach(block => { + const patch = this.findPatch(block.pos) + if (patch && block) { + block.data.type = DBG_ENTITIES_HIGHLIGHT_COLOR + patch.writeBlockData(block.index, block.data) + // patch.setBlock(block.pos, block.data) + } + }) + + // discard entities spawning over existing entities + const discardEntity = (entity: EntityData) => existingBoardEntities + .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) + // RepeatableEntitiesMap.instance. + return startBlockPositions + } + trimTrees() { this.availablePatches.forEach(patch => { patch.entities.forEach(entity => { @@ -116,14 +147,14 @@ export class BoardContainer extends PatchesMap { const isEntityOverlappingBoard = () => { const entityBlocks = patch.iterOverBlocks(entity.bbox) for (const block of entityBlocks) { - if (this.filterBoardBlocks(block.pos)) { + if (this.isWithinBoard(block.pos)) { return true } } return false } - if (entityCenterBlock && this.filterBoardBlocks(entityCenterBlock.pos)) { + if (entityCenterBlock && this.isWithinBoard(entityCenterBlock.pos)) { // trim entities belonging to board const diff = entityCenter.clone().sub(this.boardCenter) // const radius = 3 @@ -150,44 +181,11 @@ export class BoardContainer extends PatchesMap { }) } - genStartPosEntities() { - const existingBoardEntities = this.getBoardEntities() - const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - this.patchRange.clone().expandByScalar(WorldConfig.patchSize) - const items = distMap.iterMapItems(entityShape, this.bbox, () => 1) - for (const mapPos of items) { - const pos = asVect3(mapPos) - const patch = this.findPatch(pos) - const block = patch?.getBlock(pos, false) - if (patch && block) { - block.data.type = BlockType.MUD - patch.writeBlockData(block.index, block.data) - // patch.setBlock(block.pos, block.data) - } - } - // discard entities from spawning over existing entities - const discardEntity = (entity: EntityData) => existingBoardEntities - .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) - // RepeatableEntitiesMap.instance. - } - - genHoleEntities() { - - } - - highlightStartPos() { - - } - digHoles() { } - exportStub() { - const origin = this.bbox.min.clone() - const dimensions = this.bbox.getSize(new Vector3()) - const { x, z } = dimensions - const size = { x, z } + exportBoard() { // const data = // const boardData = { // origin, diff --git a/src/procgen/RandomDistributionMap.ts b/src/procgen/RandomDistributionMap.ts index 8079fdc..def4ba4 100644 --- a/src/procgen/RandomDistributionMap.ts +++ b/src/procgen/RandomDistributionMap.ts @@ -1,10 +1,11 @@ import PoissonDiskSampling from 'poisson-disk-sampling' import alea from 'alea' -import { Box2, Vector2 } from 'three' +import { Box2, Box3, Vector2 } from 'three' import { ProcLayer } from './ProcLayer' import { EntityData } from '../common/types' import { WorldConfig } from '../config/WorldConfig' +import { patchIdFromPos } from '../common/utils' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' @@ -93,46 +94,40 @@ export class PseudoDistributionMap { } /** - * Either whole area or individual point overlapping with entity - * @param input - * @param entityMask + * + * @param entityShaper + * @param inputPointOrRange either test point or range box + * @param spawnProbabilityOverride + * @returns all locations from which entity contains input point or overlaps with range box */ - *iterMapItems(entityShaper: (centerPos: Vector2) => Box2, + getSpawnLocations(entityShaper: (centerPos: Vector2) => Box2, inputPointOrRange: Vector2 | Box2, spawnProbabilityOverride?: (entityPos?: Vector2) => number, // entityMask = (_entity: EntityData) => false ) { const { dimensions } = this - const mapOrigin = inputPointOrRange instanceof Box2 ? inputPointOrRange.min : inputPointOrRange - const mapShifting = new Vector2( - Math.floor(mapOrigin.x / dimensions.x), - Math.floor(mapOrigin.y / dimensions.y), - ).multiply(dimensions) - const mapDims = - inputPointOrRange instanceof Box2 - ? inputPointOrRange.getSize(new Vector2()) - : new Vector2(1, 1) - const virtualMapStart = mapOrigin.clone().sub(mapShifting) - const virtualMapEnd = virtualMapStart.clone().add(mapDims) - const virtualMapBox = new Box2(virtualMapStart, virtualMapEnd) - const toRealMapPos = (virtualMapRelativePos: Vector2) => - mapShifting.clone().add(virtualMapRelativePos) - - // filter all items belonging to map area or intersecting point - const pointCandidates = this.points - .filter(entityCenter => { - const entityBox = entityShaper(entityCenter) - return virtualMapBox ? entityBox.intersectsBox(virtualMapBox) - : entityBox.containsPoint(mapOrigin) - }) - // .filter(entity => !entityMask(entity))// discard entities according to optional provided mask - - for (const entityCenter of pointCandidates) { - const entityPos = toRealMapPos(entityCenter) - if (this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) { - yield entityPos + const inputBox = inputPointOrRange instanceof Box2 ? inputPointOrRange : + new Box2().setFromPoints([inputPointOrRange]) + const mapRangeMin = patchIdFromPos(inputBox.min, dimensions) + const mapRangeMax = inputBox.max.clone().divide(dimensions).ceil() + const mapOffset = mapRangeMin.clone() + const candidates: Vector2[] = [] + // iter maps on computed range + for (mapOffset.x = mapRangeMin.x; mapOffset.x < mapRangeMax.x; mapOffset.x++) { + for (mapOffset.y = mapRangeMin.y; mapOffset.y < mapRangeMax.y; mapOffset.y++) { + const posOffset = mapOffset.clone().multiply(dimensions) + // convet relative pos to global pos + this.points.map(point => point.clone().add(posOffset)) + .filter(entityPos => { + const entityBox = entityShaper(entityPos) + return inputPointOrRange instanceof Vector2 ? entityBox.containsPoint(inputPointOrRange) : + entityBox.intersectsBox(inputBox) + }) + .forEach(entityPos => candidates.push(entityPos)) } } + const spawned = candidates.filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) + return spawned } // /** diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 16fcebc..3fb0d8b 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -7,7 +7,7 @@ import { BlockType } from '../index' import { TreeGenerators } from './TreeGenerator' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.SAND // disabled if NONE +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { From 714adb104e4bf76dfb49a29f376df66b0fe47d09 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 30 Aug 2024 09:57:19 +0000 Subject: [PATCH 25/45] refactor: misc (split commit #1) --- src/api/WorldComputeApi.ts | 12 +- src/api/world-compute.ts | 15 ++- src/common/utils.ts | 13 +- src/datacontainers/BlocksPatch.ts | 14 +-- src/datacontainers/BoardMap.ts | 19 ++- .../{CacheMap.ts => GroundPatchesMap.ts} | 16 +-- .../{DataContainers.ts => PatchesMap.ts} | 51 ++------ .../RandomDistributionMap.ts | 112 +++++++----------- src/index.ts | 8 +- src/procgen/BlueNoisePattern.ts | 80 +++++++++++++ 10 files changed, 182 insertions(+), 158 deletions(-) rename src/datacontainers/{CacheMap.ts => GroundPatchesMap.ts} (91%) rename src/datacontainers/{DataContainers.ts => PatchesMap.ts} (82%) rename src/{procgen => datacontainers}/RandomDistributionMap.ts (57%) create mode 100644 src/procgen/BlueNoisePattern.ts diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeApi.ts index bc0d77d..f29f575 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -1,7 +1,7 @@ import { Vector3 } from 'three' import { Block, PatchKey } from '../common/types' -import { BlocksPatchContainer, WorldCompute, WorldUtils } from '../index' +import { BlocksPatch, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { PatchCompute = 'computePatch', @@ -21,12 +21,12 @@ interface ComputeApiInterface { blockPosBatch: Vector3[], params?: any, ): Block[] | Promise - // computePatch(patchKey: PatchKey): BlocksPatchContainer | Promise + // computePatch(patchKey: PatchKey): BlocksPatch | Promise iterPatchCompute( patchKeysBatch: PatchKey[], ): - | Generator - | AsyncGenerator + | Generator + | AsyncGenerator } export class WorldComputeApi implements ComputeApiInterface { @@ -121,12 +121,12 @@ export class WorldComputeProxy implements ComputeApiInterface { async *iterPatchCompute(patchKeysBatch: PatchKey[]) { for (const patchKey of patchKeysBatch) { - // const emptyPatch = new BlocksPatchContainer(patchKey) + // const emptyPatch = new BlocksPatch(patchKey) const patchStub = await this.workerCall( ComputeApiCall.PatchCompute, [patchKey], // [emptyPatch.bbox] ) - const patch = BlocksPatchContainer.fromStub(patchStub) + const patch = BlocksPatch.fromStub(patchStub) yield patch } } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 51f38ad..d8dc0f7 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -4,7 +4,7 @@ import { ChunkFactory, EntityType, PseudoDistributionMap } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' import { - BlockData, BlocksPatchContainer, + BlockData, BlocksPatch, } from '../datacontainers/BlocksPatch' import { Block, EntityData, PatchKey } from '../common/types' import { asBox2, asVect2, asVect3 } from '../common/utils' @@ -12,11 +12,10 @@ import { asBox2, asVect2, asVect3 } from '../common/utils' // TODO remove hardcoded entity dimensions to compute from entity type const entityDefaultDims = new Vector3(10, 20, 10) // TODO move somewhere else -const defaultDistMap = new PseudoDistributionMap() -defaultDistMap.populate() +const distributionMap = new PseudoDistributionMap() export const computePatch = (patchKey: PatchKey) => { - const patch = new BlocksPatchContainer(patchKey) + const patch = new BlocksPatch(patchKey) genGroundBlocks(patch) genEntities(patch) return patch @@ -96,7 +95,7 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { // query entities at current block const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) const mapPos = asVect2(blockPos) - const spawnLocs = defaultDistMap.getSpawnLocations(entityShaper, mapPos) + const spawnLocs = distributionMap.getSpawnLocations(entityShaper, mapPos) for (const loc of spawnLocs) { const entityPos = asVect3(loc) const entity = genEntity(entityPos) @@ -109,12 +108,12 @@ export const bakeEntities = (_entities: EntityData) => { // TODO } -const genEntities = (blocksPatch: BlocksPatchContainer) => { +const genEntities = (blocksPatch: BlocksPatch) => { // query entities on patch range const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) const mapBox = asBox2(blocksPatch.bbox) - const spawnLocs = defaultDistMap.getSpawnLocations(entityShaper, mapBox) + const spawnLocs = distributionMap.getSpawnLocations(entityShaper, mapBox) const spawnedEntities = spawnLocs .map(loc => asVect3(loc)) .map(entityPos => genEntity(entityPos)) @@ -125,7 +124,7 @@ const genEntities = (blocksPatch: BlocksPatchContainer) => { /** * Fill container with ground blocks */ -const genGroundBlocks = (blocksPatch: BlocksPatchContainer) => { +const genGroundBlocks = (blocksPatch: BlocksPatch) => { const { min, max } = blocksPatch.bbox // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z // const prng = alea(patchId) diff --git a/src/common/utils.ts b/src/common/utils.ts index b8c0f7d..fd933d9 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -256,7 +256,7 @@ const parsePatchKey = (patchKey: PatchKey) => { return patchId } -const patchIdFromPos = ( +const patchLowerId = ( position: Vector2, patchSize: Vector2, ) => { @@ -264,6 +264,14 @@ const patchIdFromPos = ( return patchId } +const patchUpperId = ( + position: Vector2, + patchSize: Vector2, +) => { + const patchId = position.clone().divide(patchSize).ceil() + return patchId +} + const serializePatchId = (patchId: PatchId) => { const { x, y } = patchId const patchKey = `${x}:${y}` @@ -333,7 +341,8 @@ export { asBox2, asBox3, parsePatchKey, - patchIdFromPos, + patchLowerId, + patchUpperId, serializePatchId, patchBoxFromKey, parseChunkKey, diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 3170495..5116a5f 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -9,13 +9,13 @@ import { asVect2, asBox3, chunkBoxFromId, - patchIdFromPos, + patchLowerId, serializePatchId, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' -import { PatchDataContainer } from './DataContainers' +import { GenericPatch } from './DataContainers' export enum BlockMode { DEFAULT, @@ -51,7 +51,7 @@ const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig. * GenericBlocksContainer * multi purpose blocks container */ -export class BlocksPatchContainer implements PatchDataContainer { +export class BlocksPatch implements GenericPatch { bbox: Box3 dimensions = new Vector3() margin = 0 @@ -74,7 +74,7 @@ export class BlocksPatchContainer implements PatchDataContainer { } duplicate() { - const copy = new BlocksPatchContainer(this.key || this.bbox)// new BlocksPatchContainer(this.bbox) + const copy = new BlocksPatch(this.key || this.bbox)// new BlocksPatch(this.bbox) this.rawDataContainer.forEach( (rawVal, i) => (copy.rawDataContainer[i] = rawVal), ) @@ -86,7 +86,7 @@ export class BlocksPatchContainer implements PatchDataContainer { } return entityCopy }) - return copy + return copy as GenericPatch } decodeBlockData(rawData: number): BlockData { @@ -364,9 +364,9 @@ export class BlocksPatchContainer implements PatchDataContainer { const bbox = parseThreeStub(patchStub.bbox) as Box3 const patchCenter = asVect2(bbox.getCenter(new Vector3)) const patchDim = asVect2(bbox.getSize(new Vector3()).round()) - const patchId = patchIdFromPos(patchCenter, patchDim) + const patchId = patchLowerId(patchCenter, patchDim) const patchKey = patchStub.key || serializePatchId(patchId) - const patch = new BlocksPatchContainer(patchKey) + const patch = new BlocksPatch(patchKey) patch.rawDataContainer = rawDataContainer patch.entities = entities .map((stub: EntityData) => ({ diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 53fd0e3..7a34d22 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -3,9 +3,9 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, WorldCacheContainer, WorldConfig } from '../index' -import { PseudoDistributionMap } from '../procgen/RandomDistributionMap' -import { BlockData, BlockMode, BlocksPatchContainer } from './BlocksPatch' -import { PatchesMap } from './DataContainers' +import { PseudoDistributionMap } from './RandomDistributionMap' +import { BlockData, BlockMode, BlocksPatch } from './BlocksPatch' +import { PatchesMap } from './PatchesMap' export type BoardStub = { bbox: Box3, @@ -20,11 +20,10 @@ const distParams = { tries: 20, } const distMap = new PseudoDistributionMap(undefined, distParams) -distMap.populate() const DBG_ENTITIES_HIGHLIGHT_COLOR = BlockType.SNOW// NONE to disable debugging -export class BoardContainer extends PatchesMap { +export class BoardContainer extends PatchesMap { boardCenter boardRadius boardMaxHeightDiff @@ -36,12 +35,12 @@ export class BoardContainer extends PatchesMap { this.boardMaxHeightDiff = maxHeightDiff const board_dims = new Vector2(radius, radius).multiplyScalar(2) this.bbox.setFromCenterAndSize(asVect2(this.boardCenter), board_dims) - this.initFromBoxAndMask(this.bbox) + this.init(this.bbox) } restoreOriginalPatches() { const original_patches_container = new PatchesMap(getDefaultPatchDim()) - original_patches_container.initFromBoxAndMask(this.bbox) + original_patches_container.init(this.bbox) original_patches_container.populateFromExisting( WorldCacheContainer.instance.availablePatches, true, @@ -112,7 +111,6 @@ export class BoardContainer extends PatchesMap { genStartPositions() { const existingBoardEntities = this.getBoardEntities() const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - this.patchRange.clone().expandByScalar(WorldConfig.patchSize) const spawnLocs = distMap.getSpawnLocations(entityShape, this.bbox, () => 1) const startBlockPositions = spawnLocs .map(loc => { @@ -132,9 +130,8 @@ export class BoardContainer extends PatchesMap { }) // discard entities spawning over existing entities - const discardEntity = (entity: EntityData) => existingBoardEntities - .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) - // RepeatableEntitiesMap.instance. + // const discardEntity = (entity: EntityData) => existingBoardEntities + // .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) return startBlockPositions } diff --git a/src/datacontainers/CacheMap.ts b/src/datacontainers/GroundPatchesMap.ts similarity index 91% rename from src/datacontainers/CacheMap.ts rename to src/datacontainers/GroundPatchesMap.ts index d36c3eb..e124bc8 100644 --- a/src/datacontainers/CacheMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -2,15 +2,15 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' import { WorldConfig } from '../config/WorldConfig' -import { BlocksPatchContainer, WorldComputeApi } from '../index' -import { PatchesMap } from './DataContainers' +import { BlocksPatch, WorldComputeApi } from '../index' +import { PatchesMap } from './PatchesMap' const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) /** * Blocks cache */ -export class CacheContainer extends PatchesMap { +export class CacheContainer extends PatchesMap { // eslint-disable-next-line no-use-before-define static cachePowRadius = 2 static cacheSize = WorldConfig.patchSize * 5 @@ -48,7 +48,7 @@ export class CacheContainer extends PatchesMap { let changesDiff if (!this.pendingRefresh) { const emptyContainer = new PatchesMap(this.patchDimensions) - emptyContainer.initFromBoxAndMask(bbox) + emptyContainer.init(bbox) changesDiff = emptyContainer.compareWith(CacheContainer.instance) const hasChanged = Object.keys(changesDiff).length > 0 @@ -57,7 +57,7 @@ export class CacheContainer extends PatchesMap { // backup patches that will remain in cache const backup = this.availablePatches.filter(patch => patch) // reinit cache - super.initFromBoxAndMask(bbox) + super.init(bbox) // restore remaining patches backup this.populateFromExisting(backup) this.builtInCache && (await this.populate(this.missingPatchKeys)) @@ -77,7 +77,7 @@ export class CacheContainer extends PatchesMap { return res } - getNearPatches(patch: BlocksPatchContainer) { + getNearPatches(patch: BlocksPatch) { const dim = patch.dimensions const patchCenter = patch.bbox.getCenter(new Vector3()) const minX = patchCenter.clone().add(new Vector3(-dim.x, 0, 0)) @@ -98,9 +98,9 @@ export class CacheContainer extends PatchesMap { maxXminZ, maxXmaxZ, ] - const patchNeighbours: BlocksPatchContainer[] = neighboursCenters + const patchNeighbours: BlocksPatch[] = neighboursCenters .map(patchCenter => this.findPatch(patchCenter)) - .filter(patch => patch) as BlocksPatchContainer[] + .filter(patch => patch) as BlocksPatch[] return patchNeighbours } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/PatchesMap.ts similarity index 82% rename from src/datacontainers/DataContainers.ts rename to src/datacontainers/PatchesMap.ts index 893727f..6892154 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/PatchesMap.ts @@ -1,32 +1,15 @@ -import { Box2, Vector3, Vector2 } from "three" +import { Box2, Vector3 } from "three" import { PatchKey } from "../common/types" -import { patchIdFromPos } from "../common/utils" -import { WorldConfig } from "../config/WorldConfig" +import { GenericPatch, GenericPatchesMap } from "./DataContainers" /** - * Generic patch data container + * Finite map made from patch aggregation */ -// GenericPatch -export interface PatchDataContainer { - key: any - bbox: any - chunkIds: any - duplicate(): PatchDataContainer | null - toChunks(): any -} -/** - * Map from patch aggregation - */ -export class PatchesMap { +export class PatchesMap extends GenericPatchesMap { bbox: Box2 = new Box2() - patchDimensions: Vector2 patchLookup: Record = {} - constructor(patchDim: Vector2) { - this.patchDimensions = patchDim - } - - initFromBoxAndMask( + init( bbox: Box2, // patchDim: Vector2, // patchBboxFilter = (patchBbox: Box3) => patchBbox, @@ -38,7 +21,7 @@ export class PatchesMap { // const range = BlocksPatch.asPatchCoords(halfDimensions) // const center = this.bbox.getCenter(new Vector3()) // const origin = BlocksPatch.asPatchCoords(center) - const { min, max } = this.patchRange + const { min, max } = this.getPatchRange() for (let { x } = min; x < max.x; x++) { for (let { y } = min; y < max.y; y++) { const patchKey = `${x}:${y}` @@ -50,19 +33,12 @@ export class PatchesMap { } } - get patchRange() { - const rangeMin = patchIdFromPos(this.bbox.min, this.patchDimensions) - const rangeMax = patchIdFromPos(this.bbox.max, this.patchDimensions).addScalar(1) - const patchRange = new Box2(rangeMin, rangeMax) - return patchRange + override getPatchRange() { + return super.getPatchRange(this.bbox) } - get externalBbox() { - const { min, max } = this.patchRange - min.multiplyScalar(WorldConfig.patchSize) - max.multiplyScalar(WorldConfig.patchSize) - const extBbox = new Box2(min, max) - return extBbox + override getRoundedBox() { + return super.getRoundedBox(this.bbox) } get count() { @@ -226,10 +202,3 @@ export class PatchesMap { // }) // } } - -/** - * Repeat patch pattern indefinitely to provide infinite map - */ -export class PatchRepeatMap extends PatchesMap { - -} \ No newline at end of file diff --git a/src/procgen/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts similarity index 57% rename from src/procgen/RandomDistributionMap.ts rename to src/datacontainers/RandomDistributionMap.ts index def4ba4..4eff4f6 100644 --- a/src/procgen/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -1,11 +1,11 @@ -import PoissonDiskSampling from 'poisson-disk-sampling' import alea from 'alea' -import { Box2, Box3, Vector2 } from 'three' +import { Box2, Vector2 } from 'three' -import { ProcLayer } from './ProcLayer' +import { ProcLayer } from '../procgen/ProcLayer' +import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' import { WorldConfig } from '../config/WorldConfig' -import { patchIdFromPos } from '../common/utils' +import { patchLowerId, patchUpperId } from '../common/utils' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' @@ -26,53 +26,11 @@ const distMapDefaults = { * level or from custom box range */ export class PseudoDistributionMap { - bbox: Box2 - params - points: Vector2[] = [] + repeatedPattern: BlueNoisePattern densityMap = new ProcLayer('treemap') constructor(bbox: Box2 = distMapDefaultBox, distParams: any = distMapDefaults) { - this.bbox = bbox - this.params = distParams - } - - get dimensions() { - return this.bbox.getSize(new Vector2()) - } - - populate() { - const { dimensions, params } = this - const prng = alea('RandomDistributionMap') - const p = new PoissonDiskSampling( - { - shape: [dimensions.x, dimensions.y], - ...params - }, - prng, - ) - this.points = p.fill() - .map(point => - new Vector2(point[0] as number, point[1] as number).round()) - // make seamless repeatable map - - const radius = params.minDistance / 2 - const edgePoints = this.points - .map(point => { - const pointCopy = point.clone() - if (point.x - radius < 0) { - pointCopy.x += dimensions.x - } else if (point.x + radius > dimensions.x) { - pointCopy.x -= dimensions.x - } - if (point.y - radius < 0) { - pointCopy.y += dimensions.y - } else if (point.y + radius > dimensions.y) { - pointCopy.y -= dimensions.y - } - return pointCopy.round().equals(point) ? null : pointCopy - }) - .filter(pointCopy => pointCopy) - edgePoints.forEach(edgePoint => edgePoint && this.points.push(edgePoint)) + this.repeatedPattern = new BlueNoisePattern(bbox, distParams) } spawnProbabilityEval(pos: Vector2) { @@ -93,41 +51,53 @@ export class PseudoDistributionMap { return hasSpawned } + getPatchIdsRange(mapArea: Box2) { + const { dimensions } = this.repeatedPattern + const rangeMin = patchLowerId(mapArea.min, dimensions) + const rangeMax = patchUpperId(mapArea.max, dimensions) + return new Box2(rangeMin, rangeMax) + } + + *iterPatchIds(mapArea: Box2) { + const patchRange = this.getPatchIdsRange(mapArea) + const patchOffset = patchRange.min.clone() + // iter elements on computed range + for (patchOffset.x = patchRange.min.x; patchOffset.x < patchRange.max.x; patchOffset.x++) { + for (patchOffset.y = patchRange.min.y; patchOffset.y < patchRange.max.y; patchOffset.y++) { + yield patchOffset + } + } + } + /** * * @param entityShaper - * @param inputPointOrRange either test point or range box + * @param inputPointOrArea either test point or range box * @param spawnProbabilityOverride * @returns all locations from which entity contains input point or overlaps with range box */ getSpawnLocations(entityShaper: (centerPos: Vector2) => Box2, - inputPointOrRange: Vector2 | Box2, + inputPointOrArea: Vector2 | Box2, spawnProbabilityOverride?: (entityPos?: Vector2) => number, // entityMask = (_entity: EntityData) => false ) { - const { dimensions } = this - const inputBox = inputPointOrRange instanceof Box2 ? inputPointOrRange : - new Box2().setFromPoints([inputPointOrRange]) - const mapRangeMin = patchIdFromPos(inputBox.min, dimensions) - const mapRangeMax = inputBox.max.clone().divide(dimensions).ceil() - const mapOffset = mapRangeMin.clone() - const candidates: Vector2[] = [] - // iter maps on computed range - for (mapOffset.x = mapRangeMin.x; mapOffset.x < mapRangeMax.x; mapOffset.x++) { - for (mapOffset.y = mapRangeMin.y; mapOffset.y < mapRangeMax.y; mapOffset.y++) { - const posOffset = mapOffset.clone().multiply(dimensions) - // convet relative pos to global pos - this.points.map(point => point.clone().add(posOffset)) - .filter(entityPos => { - const entityBox = entityShaper(entityPos) - return inputPointOrRange instanceof Vector2 ? entityBox.containsPoint(inputPointOrRange) : - entityBox.intersectsBox(inputBox) - }) - .forEach(entityPos => candidates.push(entityPos)) + const mapBox = inputPointOrArea instanceof Box2 ? inputPointOrArea : + new Box2().setFromPoints([inputPointOrArea]) + const overlappingEntities: Vector2[] = [] + const patchIds = this.iterPatchIds(mapBox) + for (const patchId of patchIds) { + const patchElements = this.repeatedPattern.iterPatchElements(patchId) + // look for entities overlapping with input point or area + for (const entityPos of patchElements) { + const entityBox = entityShaper(entityPos) + const isOverlappingEntity = inputPointOrArea instanceof Vector2 ? entityBox.containsPoint(inputPointOrArea) : + entityBox.intersectsBox(mapBox) + if (isOverlappingEntity) overlappingEntities.push(entityPos) } } - const spawned = candidates.filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) - return spawned + const spawnedEntities = overlappingEntities + .filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) + return spawnedEntities } // /** diff --git a/src/index.ts b/src/index.ts index c7a7796..3f5e8f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,13 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' -export { PseudoDistributionMap } from './procgen/RandomDistributionMap' +export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' export { BoardContainer } from './datacontainers/BoardMap' export { Biome, BlockType } from './procgen/Biome' export { EntityType } from './common/types' -export { PatchesMap } from './datacontainers/DataContainers' -export { BlockMode, BlocksPatchContainer } from './datacontainers/BlocksPatch' -export { CacheContainer as WorldCacheContainer } from './datacontainers/CacheMap' +export { PatchesMap } from './datacontainers/PatchesMap' +export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' +export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' export { ChunkFactory } from './tools/ChunkFactory' export { WorldConfig } from './config/WorldConfig' export { WorldComputeApi } from './api/WorldComputeApi' diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts new file mode 100644 index 0000000..812f9cc --- /dev/null +++ b/src/procgen/BlueNoisePattern.ts @@ -0,0 +1,80 @@ +import alea from "alea" +import PoissonDiskSampling from "poisson-disk-sampling" +import { Box2, Vector2 } from "three" + +/** + * Self repeating seamless pattern + */ +export class BlueNoisePattern { + bbox: Box2 + params + elements: Vector2[] = [] + + constructor(bbox: Box2, distParams: any) { + this.bbox = bbox + this.params = distParams + this.populate() + } + + get dimensions() { + return this.bbox.getSize(new Vector2()) + } + + // populate with discrete elements using relative pos + populate() { + const { dimensions, params } = this + const prng = alea('RandomDistributionMap') + const p = new PoissonDiskSampling( + { + shape: [dimensions.x, dimensions.y], + ...params + }, + prng, + ) + this.elements = p.fill() + .map(point => + new Vector2(point[0] as number, point[1] as number).round()) + this.makeSeamless() + } + + // make seamless repeatable pattern + makeSeamless() { + const { dimensions, params } = this + const radius = params.minDistance / 2 + const edgePoints = this.elements + .map(point => { + const pointCopy = point.clone() + if (point.x - radius < 0) { + pointCopy.x += dimensions.x + } else if (point.x + radius > dimensions.x) { + pointCopy.x -= dimensions.x + } + if (point.y - radius < 0) { + pointCopy.y += dimensions.y + } else if (point.y + radius > dimensions.y) { + pointCopy.y -= dimensions.y + } + return pointCopy.round().equals(point) ? null : pointCopy + }) + .filter(pointCopy => pointCopy) + edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) + } + + getPatchOrigin(patchId: Vector2) { + return patchId.clone().multiply(this.dimensions) + } + toPatchLocalPos(pos: Vector2, patchId: Vector2) { + return pos.clone().sub(this.getPatchOrigin(patchId)) + } + toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { + return relativePos.clone().add(this.getPatchOrigin(patchId)) + } + + *iterPatchElements(patchOffset: Vector2) { + // relative to global pos conv + for (const relativePos of this.elements) { + const pos = this.toPatchGlobalPos(relativePos, patchOffset) + yield pos + } + } +} \ No newline at end of file From e81426a8bb95417b4bd6b05022de7d3c64fb86e7 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 30 Aug 2024 12:19:41 +0000 Subject: [PATCH 26/45] refactor: end split commit --- src/datacontainers/DataContainers.ts | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/datacontainers/DataContainers.ts diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts new file mode 100644 index 0000000..4067485 --- /dev/null +++ b/src/datacontainers/DataContainers.ts @@ -0,0 +1,42 @@ +/** + * Generic patch data container + */ + +import { Vector2, Box2, Vector3 } from "three" +import { patchLowerId, patchUpperId } from "../common/utils" + +// GenericPatch +export interface GenericPatch { + key: any + bbox: any + chunkIds: any + duplicate(): GenericPatch + toChunks(): any + // toLocalPos(pos: T): T + // toGlobalPos(pos: T): T + toLocalPos(pos: Vector3): Vector3 + toGlobalPos(pos: Vector3): Vector3 +} + +/** + * Generic PatchesMap + */ +export class GenericPatchesMap { + patchDimensions: Vector2 + constructor(patchDim: Vector2) { + this.patchDimensions = patchDim + } + getPatchRange(bbox: Box2) { + const rangeMin = patchLowerId(bbox.min, this.patchDimensions) + const rangeMax = patchUpperId(bbox.max, this.patchDimensions) + const patchRange = new Box2(rangeMin, rangeMax) + return patchRange + } + getRoundedBox(bbox: Box2) { + const { min, max } = this.getPatchRange(bbox) + min.multiply(this.patchDimensions) + max.multiply(this.patchDimensions) + const extBbox = new Box2(min, max) + return extBbox + } +} \ No newline at end of file From 4e5c47548a95da72805080b73eb52928d6dec1ab Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 30 Aug 2024 13:28:11 +0000 Subject: [PATCH 27/45] feat: holes digging --- src/datacontainers/BoardMap.ts | 45 ++++++++++++++++----- src/datacontainers/RandomDistributionMap.ts | 6 ++- src/procgen/Biome.ts | 3 ++ src/procgen/BlueNoisePattern.ts | 3 +- src/tools/ChunkFactory.ts | 2 +- 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 7a34d22..f70ad81 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -14,14 +14,24 @@ export type BoardStub = { const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) -const distParams = { +const startPosDistParams = { + aleaSeed: 'boardStartPos', minDistance: 10, maxDistance: 16, tries: 20, } -const distMap = new PseudoDistributionMap(undefined, distParams) +const startPosDistMap = new PseudoDistributionMap(undefined, startPosDistParams) -const DBG_ENTITIES_HIGHLIGHT_COLOR = BlockType.SNOW// NONE to disable debugging +const holesDistParams = { + aleaSeed: 'boardHoles', + minDistance: 10, + maxDistance: 16, + tries: 20, +} +const holesDistMap = new PseudoDistributionMap(undefined, holesDistParams) + +const DBG_STARTPOS_HIGHLIGHT_COLOR = BlockType.DBG_ORANGE // use NONE to disable +const DBG_HOLES_HIGHLIGHT_COLOR = BlockType.DBG_PURPLE // use NONE to disable export class BoardContainer extends PatchesMap { boardCenter @@ -109,9 +119,8 @@ export class BoardContainer extends PatchesMap { } genStartPositions() { - const existingBoardEntities = this.getBoardEntities() const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - const spawnLocs = distMap.getSpawnLocations(entityShape, this.bbox, () => 1) + const spawnLocs = startPosDistMap.getSpawnLocations(entityShape, this.bbox, () => 1) const startBlockPositions = spawnLocs .map(loc => { const startPos = asVect3(loc) @@ -120,15 +129,15 @@ export class BoardContainer extends PatchesMap { return block }) .filter(startBlock => startBlock && this.isWithinBoard(startBlock.pos)) as PatchBlock[] - DBG_ENTITIES_HIGHLIGHT_COLOR && startBlockPositions.forEach(block => { + DBG_STARTPOS_HIGHLIGHT_COLOR && startBlockPositions.forEach(block => { const patch = this.findPatch(block.pos) if (patch && block) { - block.data.type = DBG_ENTITIES_HIGHLIGHT_COLOR + block.data.type = DBG_STARTPOS_HIGHLIGHT_COLOR patch.writeBlockData(block.index, block.data) // patch.setBlock(block.pos, block.data) } }) - + // const existingBoardEntities = this.getBoardEntities() // discard entities spawning over existing entities // const discardEntity = (entity: EntityData) => existingBoardEntities // .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) @@ -179,7 +188,25 @@ export class BoardContainer extends PatchesMap { } digHoles() { - + const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) + const spawnLocs = holesDistMap.getSpawnLocations(entityShape, this.bbox, () => 1) + const startBlockPositions = spawnLocs + .map(loc => { + const startPos = asVect3(loc) + const patch = this.findPatch(startPos) + const block = patch?.getBlock(startPos, false) + return block + }) + .filter(startBlock => startBlock && this.isWithinBoard(startBlock.pos)) as PatchBlock[] + DBG_HOLES_HIGHLIGHT_COLOR && startBlockPositions.forEach(block => { + const patch = this.findPatch(block.pos) + if (patch && block) { + block.data.type = DBG_HOLES_HIGHLIGHT_COLOR + block.data.level -= 1 + patch.writeBlockData(block.index, block.data) + // patch.setBlock(block.pos, block.data) + } + }) } exportBoard() { diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 4eff4f6..5f2ce60 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -14,6 +14,7 @@ const bmin = new Vector2(0, 0) const bmax = new Vector2(WorldConfig.defaultDistMapPeriod, WorldConfig.defaultDistMapPeriod) const distMapDefaultBox = new Box2(bmin, bmax) const distMapDefaults = { + aleaSeed: 'treeMap', minDistance: 8, maxDistance: 100, tries: 20, @@ -27,10 +28,11 @@ const distMapDefaults = { */ export class PseudoDistributionMap { repeatedPattern: BlueNoisePattern - densityMap = new ProcLayer('treemap') + densityMap: ProcLayer constructor(bbox: Box2 = distMapDefaultBox, distParams: any = distMapDefaults) { this.repeatedPattern = new BlueNoisePattern(bbox, distParams) + this.densityMap = new ProcLayer(distParams.aleaSeed || '') } spawnProbabilityEval(pos: Vector2) { @@ -96,7 +98,7 @@ export class PseudoDistributionMap { } } const spawnedEntities = overlappingEntities - .filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) + .filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) return spawnedEntities } diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 57a3623..547be3d 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -25,6 +25,9 @@ export enum BlockType { MUD, ROCK, SNOW, + DBG_PURPLE, + DBG_ORANGE, + DBG_BEIGE } export enum BiomeType { diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index 812f9cc..b290375 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -23,7 +23,8 @@ export class BlueNoisePattern { // populate with discrete elements using relative pos populate() { const { dimensions, params } = this - const prng = alea('RandomDistributionMap') + const { aleaSeed } = this.params + const prng = alea(aleaSeed || '') const p = new PoissonDiskSampling( { shape: [dimensions.x, dimensions.y], diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 3fb0d8b..5244e35 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -7,7 +7,7 @@ import { BlockType } from '../index' import { TreeGenerators } from './TreeGenerator' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // disabled if NONE +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // use NONE to disable // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { From 6bf1d99a4baad773cd80b84622bc9918cae10c92 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 30 Aug 2024 15:57:55 +0000 Subject: [PATCH 28/45] fix: lint/types --- src/api/world-compute.ts | 37 ++--- src/common/utils.ts | 15 +-- src/datacontainers/BlocksPatch.ts | 107 +++++++++------ src/datacontainers/BoardMap.ts | 94 ++++++++----- src/datacontainers/DataContainers.ts | 62 +++++---- src/datacontainers/GroundPatchesMap.ts | 10 +- src/datacontainers/PatchesMap.ts | 37 ++--- src/datacontainers/RandomDistributionMap.ts | 61 ++++++--- src/index.ts | 1 - src/procgen/Biome.ts | 2 +- src/procgen/BlueNoisePattern.ts | 142 ++++++++++---------- src/tools/ChunkFactory.ts | 16 ++- 12 files changed, 327 insertions(+), 257 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index d8dc0f7..4fb8d82 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -3,9 +3,7 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { ChunkFactory, EntityType, PseudoDistributionMap } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { - BlockData, BlocksPatch, -} from '../datacontainers/BlocksPatch' +import { BlockData, BlocksPatch } from '../datacontainers/BlocksPatch' import { Block, EntityData, PatchKey } from '../common/types' import { asBox2, asVect2, asVect3 } from '../common/utils' @@ -76,15 +74,20 @@ const genEntity = (entityPos: Vector3) => { const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) const entityType = blockTypes.entities?.[0] as EntityType if (entityType) { - entityPos.y = Heightmap.instance.getGroundLevel(entityPos, rawVal) + entityDefaultDims.y / 2 - const entityBox = new Box3().setFromCenterAndSize(entityPos, entityDefaultDims) + entityPos.y = + Heightmap.instance.getGroundLevel(entityPos, rawVal) + + entityDefaultDims.y / 2 + const entityBox = new Box3().setFromCenterAndSize( + entityPos, + entityDefaultDims, + ) entity = { type: entityType, bbox: entityBox, params: { radius: 5, - size: 10 - } + size: 10, + }, } } return entity @@ -93,25 +96,29 @@ const genEntity = (entityPos: Vector3) => { export const computeBlocksBuffer = (blockPos: Vector3) => { let blocksBuffer // query entities at current block - const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) + const entityShaper = (entityPos: Vector2) => + new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) const mapPos = asVect2(blockPos) const spawnLocs = distributionMap.getSpawnLocations(entityShaper, mapPos) for (const loc of spawnLocs) { const entityPos = asVect3(loc) const entity = genEntity(entityPos) - blocksBuffer = entity ? ChunkFactory.chunkifyEntity(entity, blockPos).data : blocksBuffer + blocksBuffer = entity + ? ChunkFactory.chunkifyEntity(entity, blockPos).data + : blocksBuffer } return blocksBuffer || [] } -export const bakeEntities = (_entities: EntityData) => { - // TODO -} +// export const bakeEntities = (_entities: EntityData) => { +// // TODO +// } const genEntities = (blocksPatch: BlocksPatch) => { // query entities on patch range - const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type - const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) + const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type + const entityShaper = (entityPos: Vector2) => + new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) const mapBox = asBox2(blocksPatch.bbox) const spawnLocs = distributionMap.getSpawnLocations(entityShaper, mapBox) const spawnedEntities = spawnLocs @@ -130,7 +137,7 @@ const genGroundBlocks = (blocksPatch: BlocksPatch) => { // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const patchBlocks = blocksPatch.iterOverBlocks(undefined, false,) + const patchBlocks = blocksPatch.iterOverBlocks(undefined, false) min.y = 512 max.y = 0 let blockIndex = 0 diff --git a/src/common/utils.ts b/src/common/utils.ts index fd933d9..c6cde59 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -256,18 +256,12 @@ const parsePatchKey = (patchKey: PatchKey) => { return patchId } -const patchLowerId = ( - position: Vector2, - patchSize: Vector2, -) => { +const patchLowerId = (position: Vector2, patchSize: Vector2) => { const patchId = position.clone().divide(patchSize).floor() return patchId } -const patchUpperId = ( - position: Vector2, - patchSize: Vector2, -) => { +const patchUpperId = (position: Vector2, patchSize: Vector2) => { const patchId = position.clone().divide(patchSize).ceil() return patchId } @@ -278,10 +272,7 @@ const serializePatchId = (patchId: PatchId) => { return patchKey } -const patchBoxFromKey = ( - patchKey: string, - patchDims: Vector2, -) => { +const patchBoxFromKey = (patchKey: string, patchDims: Vector2) => { const patchCoords = parsePatchKey(patchKey) const bmin = patchCoords.clone().multiply(patchDims) const bmax = patchCoords.clone().addScalar(1).multiply(patchDims) diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 5116a5f..d9c1536 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -1,6 +1,12 @@ import { Box3, Vector2, Vector3 } from 'three' -import { Block, PatchBlock, WorldChunk, ChunkDataContainer, EntityData, } from '../common/types' +import { + Block, + PatchBlock, + WorldChunk, + ChunkDataContainer, + EntityData, +} from '../common/types' import { patchBoxFromKey, parsePatchKey, @@ -11,10 +17,12 @@ import { chunkBoxFromId, patchLowerId, serializePatchId, + asBox2, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { WorldConfig } from '../config/WorldConfig' import { ChunkFactory } from '../index' + import { GenericPatch } from './DataContainers' export enum BlockMode { @@ -45,7 +53,8 @@ const BlockDataBitAllocation = { export type BlockIteratorRes = IteratorResult -const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) +const getDefaultPatchDim = () => + new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) /** * GenericBlocksContainer @@ -63,8 +72,10 @@ export class BlocksPatch implements GenericPatch { id: Vector2 | null constructor(patchBoxOrKey: Box3 | string, margin = 1) { - this.bbox = patchBoxOrKey instanceof Box3 ? patchBoxOrKey.clone() : - asBox3(patchBoxFromKey(patchBoxOrKey, getDefaultPatchDim())) + this.bbox = + patchBoxOrKey instanceof Box3 + ? patchBoxOrKey.clone() + : asBox3(patchBoxFromKey(patchBoxOrKey, getDefaultPatchDim())) this.key = patchBoxOrKey instanceof Box3 ? null : patchBoxOrKey this.id = this.key ? parsePatchKey(this.key) : null this.bbox.getSize(this.dimensions) @@ -74,18 +85,17 @@ export class BlocksPatch implements GenericPatch { } duplicate() { - const copy = new BlocksPatch(this.key || this.bbox)// new BlocksPatch(this.bbox) + const copy = new BlocksPatch(this.key || this.bbox) // new BlocksPatch(this.bbox) this.rawDataContainer.forEach( (rawVal, i) => (copy.rawDataContainer[i] = rawVal), ) - copy.entities = this.entities - .map(entity => { - const entityCopy: EntityData = { - ...entity, - bbox: entity.bbox.clone(), - } - return entityCopy - }) + copy.entities = this.entities.map(entity => { + const entityCopy: EntityData = { + ...entity, + bbox: entity.bbox.clone(), + } + return entityCopy + }) return copy as GenericPatch } @@ -131,10 +141,7 @@ export class BlocksPatch implements GenericPatch { } get localBox() { - const localBox = new Box3( - new Vector3(0), - this.dimensions.clone(), - ) + const localBox = new Box3(new Vector3(0), this.dimensions.clone()) return localBox } @@ -143,21 +150,26 @@ export class BlocksPatch implements GenericPatch { } isWithinLocalRange(localPos: Vector3) { - return localPos.x >= 0 && + return ( + localPos.x >= 0 && localPos.x < this.dimensions.x && localPos.z >= 0 && localPos.z < this.dimensions.z + ) } isWithinGlobalRange(globalPos: Vector3) { - return globalPos.x >= this.bbox.min.x && + return ( + globalPos.x >= this.bbox.min.x && globalPos.x < this.bbox.max.x && globalPos.z >= this.bbox.min.z && globalPos.z < this.bbox.max.z + ) } adjustRangeBox(rangeBox: Box3 | Vector3, local = false) { - rangeBox = rangeBox instanceof Box3 ? rangeBox : new Box3(rangeBox, rangeBox) + rangeBox = + rangeBox instanceof Box3 ? rangeBox : new Box3(rangeBox, rangeBox) const { min, max } = local ? this.localBox : this.bbox const rangeMin = new Vector3( Math.max(Math.floor(rangeBox.min.x), min.x), @@ -169,7 +181,9 @@ export class BlocksPatch implements GenericPatch { 0, Math.min(Math.floor(rangeBox.max.z), max.z), ) - return local ? new Box3(rangeMin, rangeMax) : new Box3(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) + return local + ? new Box3(rangeMin, rangeMax) + : new Box3(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) } getBlockIndex(localPos: Vector3) { @@ -193,8 +207,9 @@ export class BlocksPatch implements GenericPatch { } getBlock(inputPos: Vector3, isLocalPos = true) { - const isWithingRange = isLocalPos ? this.isWithinLocalRange(inputPos) : - this.isWithinGlobalRange(inputPos) + const isWithingRange = isLocalPos + ? this.isWithinLocalRange(inputPos) + : this.isWithinGlobalRange(inputPos) let block: PatchBlock | undefined if (isWithingRange) { const localPos = isLocalPos ? inputPos : this.toLocalPos(inputPos) @@ -214,8 +229,9 @@ export class BlocksPatch implements GenericPatch { } setBlock(pos: Vector3, blockData: BlockData, isLocalPos = false) { - const isWithingPatch = isLocalPos ? this.isWithinLocalRange(pos) : - this.isWithinGlobalRange(pos) + const isWithingPatch = isLocalPos + ? this.isWithinLocalRange(pos) + : this.isWithinGlobalRange(pos) if (isWithingPatch) { const localPos = isLocalPos ? pos : this.toLocalPos(pos) const blockIndex = this.getBlockIndex(localPos) @@ -238,9 +254,9 @@ export class BlocksPatch implements GenericPatch { // } /** - * + * * @param rangeBox iteration range as global coords - * @param skipMargin + * @param skipMargin */ *iterOverBlocks(rangeBox?: Box3 | Vector3, skipMargin = true) { // convert to local coords to speed up iteration @@ -277,13 +293,14 @@ export class BlocksPatch implements GenericPatch { } } - containsBlock(blockPos: Vector3) { - return ( - blockPos.x >= this.bbox.min.x && - blockPos.z >= this.bbox.min.z && - blockPos.x < this.bbox.max.x && - blockPos.z < this.bbox.max.z - ) + containsPoint(blockPos: Vector3) { + return asBox2(this.bbox).containsPoint(asVect2(blockPos)) + // return ( + // blockPos.x >= this.bbox.min.x && + // blockPos.z >= this.bbox.min.z && + // blockPos.x < this.bbox.max.x && + // blockPos.z < this.bbox.max.z + // ) } *iterEntityChunkBlocks(entityChunk: ChunkDataContainer) { @@ -294,13 +311,18 @@ export class BlocksPatch implements GenericPatch { for (const block of blocks) { // const buffer = entityChunk.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) const chunkLocalPos = block.pos.clone().sub(entityChunk.bbox.min) - const buffIndex = chunkLocalPos.z * entityDims.x * entityDims.y + chunkLocalPos.x * entityDims.y + const buffIndex = + chunkLocalPos.z * entityDims.x * entityDims.y + + chunkLocalPos.x * entityDims.y block.buffer = entityChunk.data.slice(buffIndex, buffIndex + entityDims.y) const buffOffset = entityChunk.bbox.min.y - block.pos.y const buffSrc = Math.abs(Math.min(0, buffOffset)) const buffDest = Math.max(buffOffset, 0) block.buffer = block.buffer?.copyWithin(buffDest, buffSrc) - block.buffer = buffOffset < 0 ? block.buffer?.fill(BlockType.NONE, buffOffset) : block.buffer + block.buffer = + buffOffset < 0 + ? block.buffer?.fill(BlockType.NONE, buffOffset) + : block.buffer // block.buffer = new Array(20).fill(BlockType.TREE_TRUNK) yield block } @@ -324,7 +346,7 @@ export class BlocksPatch implements GenericPatch { for (const entity of this.entities) { // const entityChunk = this.buildEntityChunk(entity) const entityChunk = ChunkFactory.chunkifyEntity(entity) - const entityDataIterator = this.iterEntityChunkBlocks(entityChunk) //this.iterEntityBlocks(entity) + const entityDataIterator = this.iterEntityChunkBlocks(entityChunk) // this.iterEntityBlocks(entity) totalWrittenBlocks += ChunkFactory.default.mergeEntitiesData( entityDataIterator, chunkData, @@ -362,17 +384,16 @@ export class BlocksPatch implements GenericPatch { static fromStub(patchStub: any) { const { rawDataContainer, entities } = patchStub const bbox = parseThreeStub(patchStub.bbox) as Box3 - const patchCenter = asVect2(bbox.getCenter(new Vector3)) + const patchCenter = asVect2(bbox.getCenter(new Vector3())) const patchDim = asVect2(bbox.getSize(new Vector3()).round()) const patchId = patchLowerId(patchCenter, patchDim) const patchKey = patchStub.key || serializePatchId(patchId) const patch = new BlocksPatch(patchKey) patch.rawDataContainer = rawDataContainer - patch.entities = entities - .map((stub: EntityData) => ({ - ...stub, - bbox: parseThreeStub(stub.bbox), - })) + patch.entities = entities.map((stub: EntityData) => ({ + ...stub, + bbox: parseThreeStub(stub.bbox), + })) patch.bbox.min.y = patchStub.bbox.min.y patch.bbox.max.y = patchStub.bbox.max.y // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index f70ad81..1f1a42e 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -3,16 +3,18 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, WorldCacheContainer, WorldConfig } from '../index' + import { PseudoDistributionMap } from './RandomDistributionMap' import { BlockData, BlockMode, BlocksPatch } from './BlocksPatch' import { PatchesMap } from './PatchesMap' export type BoardStub = { - bbox: Box3, + bbox: Box3 data: BlockData } -const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) +const getDefaultPatchDim = () => + new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) const startPosDistParams = { aleaSeed: 'boardStartPos', @@ -63,7 +65,8 @@ export class BoardContainer extends PatchesMap { if (blockPos) { const heightDiff = Math.abs(blockPos.y - this.boardCenter.y) const dist = asVect2(blockPos).distanceTo(asVect2(this.boardCenter)) - isInsideBoard = dist <= this.boardRadius && heightDiff <= this.boardMaxHeightDiff + isInsideBoard = + dist <= this.boardRadius && heightDiff <= this.boardMaxHeightDiff } return isInsideBoard } @@ -101,7 +104,10 @@ export class BoardContainer extends PatchesMap { const entities: EntityData[] = [] for (const patch of this.availablePatches) { patch.entities.forEach(entity => { - if (!skipDuplicate || !entities.find(ent => ent.bbox.equals(entity.bbox))) { + if ( + !skipDuplicate || + !entities.find(ent => ent.bbox.equals(entity.bbox)) + ) { entities.push(entity) } }) @@ -110,17 +116,21 @@ export class BoardContainer extends PatchesMap { } getBoardEntities() { - const boardEntities = this.getAllPatchesEntities() - .filter(ent => { - const entityCenter = ent.bbox.getCenter(new Vector3()) - return this.isWithinBoard(entityCenter) - }) + const boardEntities = this.getAllPatchesEntities().filter(ent => { + const entityCenter = ent.bbox.getCenter(new Vector3()) + return this.isWithinBoard(entityCenter) + }) return boardEntities } genStartPositions() { - const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - const spawnLocs = startPosDistMap.getSpawnLocations(entityShape, this.bbox, () => 1) + const entityShape = (pos: Vector2) => + new Box2(pos, pos.clone().addScalar(2)) + const spawnLocs = startPosDistMap.getSpawnLocations( + entityShape, + this.bbox, + () => 1, + ) const startBlockPositions = spawnLocs .map(loc => { const startPos = asVect3(loc) @@ -128,15 +138,18 @@ export class BoardContainer extends PatchesMap { const block = patch?.getBlock(startPos, false) return block }) - .filter(startBlock => startBlock && this.isWithinBoard(startBlock.pos)) as PatchBlock[] - DBG_STARTPOS_HIGHLIGHT_COLOR && startBlockPositions.forEach(block => { - const patch = this.findPatch(block.pos) - if (patch && block) { - block.data.type = DBG_STARTPOS_HIGHLIGHT_COLOR - patch.writeBlockData(block.index, block.data) - // patch.setBlock(block.pos, block.data) - } - }) + .filter( + startBlock => startBlock && this.isWithinBoard(startBlock.pos), + ) as PatchBlock[] + DBG_STARTPOS_HIGHLIGHT_COLOR && + startBlockPositions.forEach(block => { + const patch = this.findPatch(block.pos) + if (patch && block) { + block.data.type = DBG_STARTPOS_HIGHLIGHT_COLOR + patch.writeBlockData(block.index, block.data) + // patch.setBlock(block.pos, block.data) + } + }) // const existingBoardEntities = this.getBoardEntities() // discard entities spawning over existing entities // const discardEntity = (entity: EntityData) => existingBoardEntities @@ -148,7 +161,10 @@ export class BoardContainer extends PatchesMap { this.availablePatches.forEach(patch => { patch.entities.forEach(entity => { const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = this.findPatch(entityCenter)?.getBlock(entityCenter, false) + const entityCenterBlock = this.findPatch(entityCenter)?.getBlock( + entityCenter, + false, + ) entityCenter.y = entity.bbox.min.y const isEntityOverlappingBoard = () => { const entityBlocks = patch.iterOverBlocks(entity.bbox) @@ -188,8 +204,13 @@ export class BoardContainer extends PatchesMap { } digHoles() { - const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - const spawnLocs = holesDistMap.getSpawnLocations(entityShape, this.bbox, () => 1) + const entityShape = (pos: Vector2) => + new Box2(pos, pos.clone().addScalar(2)) + const spawnLocs = holesDistMap.getSpawnLocations( + entityShape, + this.bbox, + () => 1, + ) const startBlockPositions = spawnLocs .map(loc => { const startPos = asVect3(loc) @@ -197,20 +218,23 @@ export class BoardContainer extends PatchesMap { const block = patch?.getBlock(startPos, false) return block }) - .filter(startBlock => startBlock && this.isWithinBoard(startBlock.pos)) as PatchBlock[] - DBG_HOLES_HIGHLIGHT_COLOR && startBlockPositions.forEach(block => { - const patch = this.findPatch(block.pos) - if (patch && block) { - block.data.type = DBG_HOLES_HIGHLIGHT_COLOR - block.data.level -= 1 - patch.writeBlockData(block.index, block.data) - // patch.setBlock(block.pos, block.data) - } - }) + .filter( + startBlock => startBlock && this.isWithinBoard(startBlock.pos), + ) as PatchBlock[] + DBG_HOLES_HIGHLIGHT_COLOR && + startBlockPositions.forEach(block => { + const patch = this.findPatch(block.pos) + if (patch && block) { + block.data.type = DBG_HOLES_HIGHLIGHT_COLOR + block.data.level -= 1 + patch.writeBlockData(block.index, block.data) + // patch.setBlock(block.pos, block.data) + } + }) } exportBoard() { - // const data = + // const data = // const boardData = { // origin, // size, @@ -218,5 +242,5 @@ export class BoardContainer extends PatchesMap { // } } - smoothEdges() { } + smoothEdges() {} } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 4067485..008ebf8 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -2,41 +2,45 @@ * Generic patch data container */ -import { Vector2, Box2, Vector3 } from "three" -import { patchLowerId, patchUpperId } from "../common/utils" +import { Vector2, Box2, Vector3 } from 'three' + +import { patchLowerId, patchUpperId } from '../common/utils' // GenericPatch export interface GenericPatch { - key: any - bbox: any - chunkIds: any - duplicate(): GenericPatch - toChunks(): any - // toLocalPos(pos: T): T - // toGlobalPos(pos: T): T - toLocalPos(pos: Vector3): Vector3 - toGlobalPos(pos: Vector3): Vector3 + key: any + bbox: any + chunkIds: any + duplicate(): GenericPatch + toChunks(): any + // toLocalPos(pos: T): T + // toGlobalPos(pos: T): T + toLocalPos(pos: Vector3): Vector3 + toGlobalPos(pos: Vector3): Vector3 + containsPoint(pos: Vector3): Boolean } /** * Generic PatchesMap */ export class GenericPatchesMap { - patchDimensions: Vector2 - constructor(patchDim: Vector2) { - this.patchDimensions = patchDim - } - getPatchRange(bbox: Box2) { - const rangeMin = patchLowerId(bbox.min, this.patchDimensions) - const rangeMax = patchUpperId(bbox.max, this.patchDimensions) - const patchRange = new Box2(rangeMin, rangeMax) - return patchRange - } - getRoundedBox(bbox: Box2) { - const { min, max } = this.getPatchRange(bbox) - min.multiply(this.patchDimensions) - max.multiply(this.patchDimensions) - const extBbox = new Box2(min, max) - return extBbox - } -} \ No newline at end of file + patchDimensions: Vector2 + constructor(patchDim: Vector2) { + this.patchDimensions = patchDim + } + + getPatchRange(bbox: Box2) { + const rangeMin = patchLowerId(bbox.min, this.patchDimensions) + const rangeMax = patchUpperId(bbox.max, this.patchDimensions) + const patchRange = new Box2(rangeMin, rangeMax) + return patchRange + } + + getRoundedBox(bbox: Box2) { + const { min, max } = this.getPatchRange(bbox) + min.multiply(this.patchDimensions) + max.multiply(this.patchDimensions) + const extBbox = new Box2(min, max) + return extBbox + } +} diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts index e124bc8..4d29d72 100644 --- a/src/datacontainers/GroundPatchesMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -1,24 +1,26 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' +import { asBox2 } from '../common/utils' import { WorldConfig } from '../config/WorldConfig' import { BlocksPatch, WorldComputeApi } from '../index' + import { PatchesMap } from './PatchesMap' -const getDefaultPatchDim = () => new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) +const getDefaultPatchDim = () => + new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) /** * Blocks cache */ export class CacheContainer extends PatchesMap { - // eslint-disable-next-line no-use-before-define static cachePowRadius = 2 static cacheSize = WorldConfig.patchSize * 5 + // eslint-disable-next-line no-use-before-define static singleton: CacheContainer pendingRefresh = false builtInCache = false // specify whether cache is managed internally or separately - static get instance() { this.singleton = this.singleton || new CacheContainer(getDefaultPatchDim()) return this.singleton @@ -31,7 +33,7 @@ export class CacheContainer extends PatchesMap { for await (const patch of batchIter) { if (patch.key) { this.patchLookup[patch.key] = patch - this.bbox.union(patch.bbox) + this.bbox.union(asBox2(patch.bbox)) } } this.pendingRefresh = false diff --git a/src/datacontainers/PatchesMap.ts b/src/datacontainers/PatchesMap.ts index 6892154..f169615 100644 --- a/src/datacontainers/PatchesMap.ts +++ b/src/datacontainers/PatchesMap.ts @@ -1,31 +1,26 @@ -import { Box2, Vector3 } from "three" -import { PatchKey } from "../common/types" -import { GenericPatch, GenericPatchesMap } from "./DataContainers" +import { Box2, Vector3 } from 'three' + +import { PatchKey } from '../common/types' + +import { GenericPatch, GenericPatchesMap } from './DataContainers' /** * Finite map made from patch aggregation */ -export class PatchesMap extends GenericPatchesMap { +export class PatchesMap extends GenericPatchesMap { bbox: Box2 = new Box2() - patchLookup: Record = {} + patchLookup: Record = {} init( bbox: Box2, - // patchDim: Vector2, // patchBboxFilter = (patchBbox: Box3) => patchBbox, ) { this.bbox = bbox - // this.patchDimensions = patchDim this.patchLookup = {} - // const halfDimensions = this.bbox.getSize(new Vector3()).divideScalar(2) - // const range = BlocksPatch.asPatchCoords(halfDimensions) - // const center = this.bbox.getCenter(new Vector3()) - // const origin = BlocksPatch.asPatchCoords(center) const { min, max } = this.getPatchRange() for (let { x } = min; x < max.x; x++) { for (let { y } = min; y < max.y; y++) { const patchKey = `${x}:${y}` - // const patchBox = patchBoxFromKey(patchKey, patchDim) // if (patchBboxFilter(patchBox)) { this.patchLookup[patchKey] = null // } @@ -54,7 +49,7 @@ export class PatchesMap extends GenericPatchesMa } get availablePatches() { - return Object.values(this.patchLookup).filter(val => val) as PatchType[] + return Object.values(this.patchLookup).filter(val => val) as T[] } get missingPatchKeys() { @@ -68,18 +63,20 @@ export class PatchesMap extends GenericPatchesMa // this.availablePatches.forEach(patch=>patch.iterOverBlocks) // } - populateFromExisting(patches: PatchType[], cloneObjects = false) { + populateFromExisting(patches: T[], cloneObjects = false) { // const { min, max } = this.bbox patches .filter(patch => this.patchLookup[patch.key] !== undefined) .forEach(patch => { - this.patchLookup[patch.key] = cloneObjects ? patch.duplicate() : patch + this.patchLookup[patch.key] = cloneObjects + ? (patch.duplicate() as T) + : patch // min.y = Math.min(patch.bbox.min.y, min.y) // max.y = Math.max(patch.bbox.max.y, max.y) }) } - compareWith(otherContainer: PatchesMap) { + compareWith(otherContainer: PatchesMap) { const patchKeysDiff: Record = {} // added keys e.g. keys in current container but not found in other Object.keys(this.patchLookup) @@ -100,14 +97,8 @@ export class PatchesMap extends GenericPatchesMa } findPatch(blockPos: Vector3) { - // const point = new Vector3( - // inputPoint.x, - // 0, - // inputPoint instanceof Vector3 ? inputPoint.z : inputPoint.y, - // ) - const res = this.availablePatches.find(patch => - patch.containsBlock(blockPos), + patch.containsPoint(blockPos), ) return res } diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 5f2ce60..005c3f5 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -11,7 +11,10 @@ import { patchLowerId, patchUpperId } from '../common/utils' const probabilityThreshold = Math.pow(2, 8) const bmin = new Vector2(0, 0) -const bmax = new Vector2(WorldConfig.defaultDistMapPeriod, WorldConfig.defaultDistMapPeriod) +const bmax = new Vector2( + WorldConfig.defaultDistMapPeriod, + WorldConfig.defaultDistMapPeriod, +) const distMapDefaultBox = new Box2(bmin, bmax) const distMapDefaults = { aleaSeed: 'treeMap', @@ -23,14 +26,17 @@ const distMapDefaults = { /** * Infinite map using repeatable seamless pattern to provide * independant, deterministic and approximated random distribution - * Enable querying/iterating randomly distributed items at block - * level or from custom box range + * Enable querying/iterating randomly distributed items at block + * level or from custom box range */ export class PseudoDistributionMap { repeatedPattern: BlueNoisePattern densityMap: ProcLayer - constructor(bbox: Box2 = distMapDefaultBox, distParams: any = distMapDefaults) { + constructor( + bbox: Box2 = distMapDefaultBox, + distParams: any = distMapDefaults, + ) { this.repeatedPattern = new BlueNoisePattern(bbox, distParams) this.densityMap = new ProcLayer(distParams.aleaSeed || '') } @@ -46,7 +52,10 @@ export class PseudoDistributionMap { hasSpawned(itemPos: Vector2, spawnProbabilty?: number) { // eval spawn probability at entity center - spawnProbabilty = spawnProbabilty && !isNaN(spawnProbabilty) ? spawnProbabilty : this.spawnProbabilityEval(itemPos) + spawnProbabilty = + spawnProbabilty && !isNaN(spawnProbabilty) + ? spawnProbabilty + : this.spawnProbabilityEval(itemPos) const itemId = itemPos.x + ':' + itemPos.y const prng = alea(itemId) const hasSpawned = prng() * spawnProbabilty < probabilityThreshold @@ -64,27 +73,38 @@ export class PseudoDistributionMap { const patchRange = this.getPatchIdsRange(mapArea) const patchOffset = patchRange.min.clone() // iter elements on computed range - for (patchOffset.x = patchRange.min.x; patchOffset.x < patchRange.max.x; patchOffset.x++) { - for (patchOffset.y = patchRange.min.y; patchOffset.y < patchRange.max.y; patchOffset.y++) { + for ( + patchOffset.x = patchRange.min.x; + patchOffset.x < patchRange.max.x; + patchOffset.x++ + ) { + for ( + patchOffset.y = patchRange.min.y; + patchOffset.y < patchRange.max.y; + patchOffset.y++ + ) { yield patchOffset } } } /** - * - * @param entityShaper + * + * @param entityShaper * @param inputPointOrArea either test point or range box - * @param spawnProbabilityOverride + * @param spawnProbabilityOverride * @returns all locations from which entity contains input point or overlaps with range box */ - getSpawnLocations(entityShaper: (centerPos: Vector2) => Box2, + getSpawnLocations( + entityShaper: (centerPos: Vector2) => Box2, inputPointOrArea: Vector2 | Box2, spawnProbabilityOverride?: (entityPos?: Vector2) => number, // entityMask = (_entity: EntityData) => false ) { - const mapBox = inputPointOrArea instanceof Box2 ? inputPointOrArea : - new Box2().setFromPoints([inputPointOrArea]) + const mapBox = + inputPointOrArea instanceof Box2 + ? inputPointOrArea + : new Box2().setFromPoints([inputPointOrArea]) const overlappingEntities: Vector2[] = [] const patchIds = this.iterPatchIds(mapBox) for (const patchId of patchIds) { @@ -92,13 +112,16 @@ export class PseudoDistributionMap { // look for entities overlapping with input point or area for (const entityPos of patchElements) { const entityBox = entityShaper(entityPos) - const isOverlappingEntity = inputPointOrArea instanceof Vector2 ? entityBox.containsPoint(inputPointOrArea) : - entityBox.intersectsBox(mapBox) + const isOverlappingEntity = + inputPointOrArea instanceof Vector2 + ? entityBox.containsPoint(inputPointOrArea) + : entityBox.intersectsBox(mapBox) if (isOverlappingEntity) overlappingEntities.push(entityPos) } } - const spawnedEntities = overlappingEntities - .filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos))) + const spawnedEntities = overlappingEntities.filter(entityPos => + this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos)), + ) return spawnedEntities } @@ -112,11 +135,11 @@ export class PseudoDistributionMap { // } } - /** * Storing entities at biome level with overlap at biomes' transitions */ -export class OverlappingEntitiesMap { //extends RandomDistributionMap { +export class OverlappingEntitiesMap { + // extends RandomDistributionMap { // entities stored per biome static biomeMapsLookup: Record = {} // getAdjacentEntities() { diff --git a/src/index.ts b/src/index.ts index 3f5e8f1..8d0957b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ - export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 547be3d..4e62c76 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -27,7 +27,7 @@ export enum BlockType { SNOW, DBG_PURPLE, DBG_ORANGE, - DBG_BEIGE + DBG_BEIGE, } export enum BiomeType { diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index b290375..f1e3bca 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -1,81 +1,83 @@ -import alea from "alea" -import PoissonDiskSampling from "poisson-disk-sampling" -import { Box2, Vector2 } from "three" +import alea from 'alea' +import PoissonDiskSampling from 'poisson-disk-sampling' +import { Box2, Vector2 } from 'three' /** - * Self repeating seamless pattern + * Self repeating seamless pattern */ export class BlueNoisePattern { - bbox: Box2 - params - elements: Vector2[] = [] + bbox: Box2 + params + elements: Vector2[] = [] - constructor(bbox: Box2, distParams: any) { - this.bbox = bbox - this.params = distParams - this.populate() - } + constructor(bbox: Box2, distParams: any) { + this.bbox = bbox + this.params = distParams + this.populate() + } - get dimensions() { - return this.bbox.getSize(new Vector2()) - } + get dimensions() { + return this.bbox.getSize(new Vector2()) + } - // populate with discrete elements using relative pos - populate() { - const { dimensions, params } = this - const { aleaSeed } = this.params - const prng = alea(aleaSeed || '') - const p = new PoissonDiskSampling( - { - shape: [dimensions.x, dimensions.y], - ...params - }, - prng, - ) - this.elements = p.fill() - .map(point => - new Vector2(point[0] as number, point[1] as number).round()) - this.makeSeamless() - } + // populate with discrete elements using relative pos + populate() { + const { dimensions, params } = this + const { aleaSeed } = this.params + const prng = alea(aleaSeed || '') + const p = new PoissonDiskSampling( + { + shape: [dimensions.x, dimensions.y], + ...params, + }, + prng, + ) + this.elements = p + .fill() + .map(point => new Vector2(point[0] as number, point[1] as number).round()) + this.makeSeamless() + } - // make seamless repeatable pattern - makeSeamless() { - const { dimensions, params } = this - const radius = params.minDistance / 2 - const edgePoints = this.elements - .map(point => { - const pointCopy = point.clone() - if (point.x - radius < 0) { - pointCopy.x += dimensions.x - } else if (point.x + radius > dimensions.x) { - pointCopy.x -= dimensions.x - } - if (point.y - radius < 0) { - pointCopy.y += dimensions.y - } else if (point.y + radius > dimensions.y) { - pointCopy.y -= dimensions.y - } - return pointCopy.round().equals(point) ? null : pointCopy - }) - .filter(pointCopy => pointCopy) - edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) - } + // make seamless repeatable pattern + makeSeamless() { + const { dimensions, params } = this + const radius = params.minDistance / 2 + const edgePoints = this.elements + .map(point => { + const pointCopy = point.clone() + if (point.x - radius < 0) { + pointCopy.x += dimensions.x + } else if (point.x + radius > dimensions.x) { + pointCopy.x -= dimensions.x + } + if (point.y - radius < 0) { + pointCopy.y += dimensions.y + } else if (point.y + radius > dimensions.y) { + pointCopy.y -= dimensions.y + } + return pointCopy.round().equals(point) ? null : pointCopy + }) + .filter(pointCopy => pointCopy) + edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) + } - getPatchOrigin(patchId: Vector2) { - return patchId.clone().multiply(this.dimensions) - } - toPatchLocalPos(pos: Vector2, patchId: Vector2) { - return pos.clone().sub(this.getPatchOrigin(patchId)) - } - toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { - return relativePos.clone().add(this.getPatchOrigin(patchId)) - } + getPatchOrigin(patchId: Vector2) { + return patchId.clone().multiply(this.dimensions) + } - *iterPatchElements(patchOffset: Vector2) { - // relative to global pos conv - for (const relativePos of this.elements) { - const pos = this.toPatchGlobalPos(relativePos, patchOffset) - yield pos - } + toPatchLocalPos(pos: Vector2, patchId: Vector2) { + return pos.clone().sub(this.getPatchOrigin(patchId)) + } + + toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { + return relativePos.clone().add(this.getPatchOrigin(patchId)) + } + + *iterPatchElements(patchOffset: Vector2) { + // relative to global pos conv + for (const relativePos of this.elements) { + const pos = this.toPatchGlobalPos(relativePos, patchOffset) + yield pos } -} \ No newline at end of file + } +} diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 5244e35..04ee52a 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -7,7 +7,7 @@ import { BlockType } from '../index' import { TreeGenerators } from './TreeGenerator' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // use NONE to disable +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.DBG_BEIGE // use NONE to disable // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -70,7 +70,7 @@ export class ChunkFactory { buff_index > 0 && chunkDataContainer[blocksIndex] !== undefined && !bufferOver[buff_index] - if (!skip) { + if (!skip && blockType !== undefined) { chunkDataContainer[blocksIndex] = this.voxelDataEncoder( blockType, blockData.mode, @@ -136,8 +136,14 @@ export class ChunkFactory { static chunkifyEntity(entity: EntityData, blockPosOrRange?: Vector3 | Box3) { if (blockPosOrRange instanceof Vector3) { - const blockStart = new Vector3(blockPosOrRange.x, entity.bbox.min.y, blockPosOrRange.z) - const blockEnd = blockStart.clone().add(new Vector3(1, entity.bbox.max.y - entity.bbox.min.y, 1)) + const blockStart = new Vector3( + blockPosOrRange.x, + entity.bbox.min.y, + blockPosOrRange.z, + ) + const blockEnd = blockStart + .clone() + .add(new Vector3(1, entity.bbox.max.y - entity.bbox.min.y, 1)) blockPosOrRange = new Box3(blockStart, blockEnd) } const range = blockPosOrRange || entity.bbox @@ -172,7 +178,7 @@ export class ChunkFactory { } const entityChunk = { bbox: range, - data + data, } return entityChunk } From f3da0ff4bb7fd1d72be02992fc20e3a2695e9661 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 30 Aug 2024 19:26:06 +0000 Subject: [PATCH 29/45] fix: remove checkerboard excess on board edges and entities --- src/api/world-compute.ts | 6 +++--- src/datacontainers/BlocksPatch.ts | 6 +++--- src/datacontainers/BoardMap.ts | 16 +++++++++------- src/datacontainers/RandomDistributionMap.ts | 2 +- src/procgen/Biome.ts | 3 ++- src/tools/ChunkFactory.ts | 12 ++++++++---- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 4fb8d82..b99300c 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -99,7 +99,7 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) const mapPos = asVect2(blockPos) - const spawnLocs = distributionMap.getSpawnLocations(entityShaper, mapPos) + const spawnLocs = distributionMap.querySpawnLocations(entityShaper, mapPos) for (const loc of spawnLocs) { const entityPos = asVect3(loc) const entity = genEntity(entityPos) @@ -120,7 +120,7 @@ const genEntities = (blocksPatch: BlocksPatch) => { const entityShaper = (entityPos: Vector2) => new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) const mapBox = asBox2(blocksPatch.bbox) - const spawnLocs = distributionMap.getSpawnLocations(entityShaper, mapBox) + const spawnLocs = distributionMap.querySpawnLocations(entityShaper, mapBox) const spawnedEntities = spawnLocs .map(loc => asVect3(loc)) .map(entityPos => genEntity(entityPos)) @@ -137,7 +137,7 @@ const genGroundBlocks = (blocksPatch: BlocksPatch) => { // const prng = alea(patchId) // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) - const patchBlocks = blocksPatch.iterOverBlocks(undefined, false) + const patchBlocks = blocksPatch.iterBlocksQuery(undefined, false) min.y = 512 max.y = 0 let blockIndex = 0 diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index d9c1536..2817b18 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -258,7 +258,7 @@ export class BlocksPatch implements GenericPatch { * @param rangeBox iteration range as global coords * @param skipMargin */ - *iterOverBlocks(rangeBox?: Box3 | Vector3, skipMargin = true) { + *iterBlocksQuery(rangeBox?: Box3 | Vector3, skipMargin = true) { // convert to local coords to speed up iteration const localBbox = rangeBox ? this.adjustRangeBox(rangeBox) @@ -306,7 +306,7 @@ export class BlocksPatch implements GenericPatch { *iterEntityChunkBlocks(entityChunk: ChunkDataContainer) { // return overlapping blocks between entity and container const entityDims = entityChunk.bbox.getSize(new Vector3()) - const blocks = this.iterOverBlocks(entityChunk.bbox) + const blocks = this.iterBlocksQuery(entityChunk.bbox) for (const block of blocks) { // const buffer = entityChunk.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) @@ -335,7 +335,7 @@ export class BlocksPatch implements GenericPatch { const chunkDims = chunkBox.getSize(new Vector3()) const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) // Ground pass - const groundBlocksIterator = this.iterOverBlocks(undefined, false) + const groundBlocksIterator = this.iterBlocksQuery(undefined, false) // ground blocks pass totalWrittenBlocks += ChunkFactory.default.fillGroundData( groundBlocksIterator, diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 1f1a42e..815a105 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -32,8 +32,8 @@ const holesDistParams = { } const holesDistMap = new PseudoDistributionMap(undefined, holesDistParams) -const DBG_STARTPOS_HIGHLIGHT_COLOR = BlockType.DBG_ORANGE // use NONE to disable -const DBG_HOLES_HIGHLIGHT_COLOR = BlockType.DBG_PURPLE // use NONE to disable +const DBG_STARTPOS_HIGHLIGHT_COLOR = BlockType.DBG_LIGHT // use NONE to disable +const DBG_HOLES_HIGHLIGHT_COLOR = BlockType.DBG_DARK // use NONE to disable export class BoardContainer extends PatchesMap { boardCenter @@ -86,7 +86,7 @@ export class BoardContainer extends PatchesMap { this.bbox.max = asVect2(this.boardCenter) for (const patch of this.availablePatches) { - const blocks = patch.iterOverBlocks(undefined, false) + const blocks = patch.iterBlocksQuery(undefined, false) // const blocks = this.iterPatchesBlocks() for (const block of blocks) { // discard blocs not included in board shape @@ -126,7 +126,7 @@ export class BoardContainer extends PatchesMap { genStartPositions() { const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - const spawnLocs = startPosDistMap.getSpawnLocations( + const spawnLocs = startPosDistMap.querySpawnLocations( entityShape, this.bbox, () => 1, @@ -146,6 +146,7 @@ export class BoardContainer extends PatchesMap { const patch = this.findPatch(block.pos) if (patch && block) { block.data.type = DBG_STARTPOS_HIGHLIGHT_COLOR + block.data.mode = BlockMode.DEFAULT patch.writeBlockData(block.index, block.data) // patch.setBlock(block.pos, block.data) } @@ -167,7 +168,7 @@ export class BoardContainer extends PatchesMap { ) entityCenter.y = entity.bbox.min.y const isEntityOverlappingBoard = () => { - const entityBlocks = patch.iterOverBlocks(entity.bbox) + const entityBlocks = patch.iterBlocksQuery(entity.bbox) for (const block of entityBlocks) { if (this.isWithinBoard(block.pos)) { return true @@ -206,7 +207,7 @@ export class BoardContainer extends PatchesMap { digHoles() { const entityShape = (pos: Vector2) => new Box2(pos, pos.clone().addScalar(2)) - const spawnLocs = holesDistMap.getSpawnLocations( + const spawnLocs = holesDistMap.querySpawnLocations( entityShape, this.bbox, () => 1, @@ -226,7 +227,8 @@ export class BoardContainer extends PatchesMap { const patch = this.findPatch(block.pos) if (patch && block) { block.data.type = DBG_HOLES_HIGHLIGHT_COLOR - block.data.level -= 1 + block.data.level -= 1 // dig hole in the ground + block.data.mode = BlockMode.DEFAULT patch.writeBlockData(block.index, block.data) // patch.setBlock(block.pos, block.data) } diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 005c3f5..280489a 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -95,7 +95,7 @@ export class PseudoDistributionMap { * @param spawnProbabilityOverride * @returns all locations from which entity contains input point or overlaps with range box */ - getSpawnLocations( + querySpawnLocations( entityShaper: (centerPos: Vector2) => Box2, inputPointOrArea: Vector2 | Box2, spawnProbabilityOverride?: (entityPos?: Vector2) => number, diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 4e62c76..007b9a7 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -25,9 +25,10 @@ export enum BlockType { MUD, ROCK, SNOW, + DBG_LIGHT, + DBG_DARK, DBG_PURPLE, DBG_ORANGE, - DBG_BEIGE, } export enum BiomeType { diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 04ee52a..766d875 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -7,7 +7,7 @@ import { BlockType } from '../index' import { TreeGenerators } from './TreeGenerator' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.DBG_BEIGE // use NONE to disable +const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // use NONE to disable // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -59,7 +59,7 @@ export class ChunkFactory { // 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) + @@ -71,14 +71,18 @@ export class ChunkFactory { chunkDataContainer[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 chunkDataContainer[blocksIndex] = this.voxelDataEncoder( blockType, - blockData.mode, + blockMode, ) blockType && written_blocks_count++ } - buff_index-- h-- + buff_index-- + depth++ } return written_blocks_count } From 39b6b843ececb922f3377bd65183ba052e1ba2ab Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 30 Aug 2024 20:23:13 +0000 Subject: [PATCH 30/45] refactor: WorldConf: move debug vars to global conf --- src/common/utils.ts | 5 ++--- src/config/WorldConfig.ts | 7 ------- src/datacontainers/BlocksPatch.ts | 7 +++---- src/datacontainers/BoardMap.ts | 15 ++++++--------- src/datacontainers/GroundPatchesMap.ts | 7 +++---- src/datacontainers/RandomDistributionMap.ts | 6 +++--- src/index.ts | 4 ++-- src/misc/WorldConfig.ts | 19 +++++++++++++++++++ src/tools/ChunkFactory.ts | 8 +++----- 9 files changed, 41 insertions(+), 37 deletions(-) delete mode 100644 src/config/WorldConfig.ts create mode 100644 src/misc/WorldConfig.ts diff --git a/src/common/utils.ts b/src/common/utils.ts index c6cde59..5c7395b 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,6 +1,5 @@ import { Box2, Box3, Vector2, Vector3, Vector3Like } from 'three' - -import { WorldConfig } from '../config/WorldConfig' +import { WorldConf } from '../index' import { Adjacent2dPos, @@ -304,7 +303,7 @@ function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { const chunkBoxFromId = ( chunkId: ChunkId, - patchSize: number = WorldConfig.patchSize, + patchSize: number = WorldConf.patchSize, ) => { const bmin = chunkId.clone().multiplyScalar(patchSize) const bmax = chunkId.clone().addScalar(1).multiplyScalar(patchSize) diff --git a/src/config/WorldConfig.ts b/src/config/WorldConfig.ts deleted file mode 100644 index 1e2c12d..0000000 --- a/src/config/WorldConfig.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class WorldConfig { - static patchSize = Math.pow(2, 6) - - // max cache radius as a power of two - static cachePowLimit = 2 // 4 => 16 patches radius - static defaultDistMapPeriod = 4 * this.patchSize -} diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 2817b18..bd52029 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -20,8 +20,7 @@ import { asBox2, } from '../common/utils' import { BlockType } from '../procgen/Biome' -import { WorldConfig } from '../config/WorldConfig' -import { ChunkFactory } from '../index' +import { ChunkFactory, WorldConf } from '../index' import { GenericPatch } from './DataContainers' @@ -54,7 +53,7 @@ const BlockDataBitAllocation = { export type BlockIteratorRes = IteratorResult const getDefaultPatchDim = () => - new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) + new Vector2(WorldConf.patchSize, WorldConf.patchSize) /** * GenericBlocksContainer @@ -370,7 +369,7 @@ export class BlocksPatch implements GenericPatch { toChunks() { const chunks = this.chunkIds.map(chunkId => { - const chunkBox = chunkBoxFromId(chunkId, WorldConfig.patchSize) + const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) const chunk = this.toChunk(chunkBox) const worldChunk: WorldChunk = { key: serializeChunkId(chunkId), diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 815a105..24b3a1c 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -2,7 +2,7 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' -import { BlockType, WorldCacheContainer, WorldConfig } from '../index' +import { WorldCacheContainer, WorldConf } from '../index' import { PseudoDistributionMap } from './RandomDistributionMap' import { BlockData, BlockMode, BlocksPatch } from './BlocksPatch' @@ -14,7 +14,7 @@ export type BoardStub = { } const getDefaultPatchDim = () => - new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) + new Vector2(WorldConf.patchSize, WorldConf.patchSize) const startPosDistParams = { aleaSeed: 'boardStartPos', @@ -32,9 +32,6 @@ const holesDistParams = { } const holesDistMap = new PseudoDistributionMap(undefined, holesDistParams) -const DBG_STARTPOS_HIGHLIGHT_COLOR = BlockType.DBG_LIGHT // use NONE to disable -const DBG_HOLES_HIGHLIGHT_COLOR = BlockType.DBG_DARK // use NONE to disable - export class BoardContainer extends PatchesMap { boardCenter boardRadius @@ -141,11 +138,11 @@ export class BoardContainer extends PatchesMap { .filter( startBlock => startBlock && this.isWithinBoard(startBlock.pos), ) as PatchBlock[] - DBG_STARTPOS_HIGHLIGHT_COLOR && + WorldConf.debug.boardStartPosHighlightColor && startBlockPositions.forEach(block => { const patch = this.findPatch(block.pos) if (patch && block) { - block.data.type = DBG_STARTPOS_HIGHLIGHT_COLOR + block.data.type = WorldConf.debug.boardStartPosHighlightColor block.data.mode = BlockMode.DEFAULT patch.writeBlockData(block.index, block.data) // patch.setBlock(block.pos, block.data) @@ -222,11 +219,11 @@ export class BoardContainer extends PatchesMap { .filter( startBlock => startBlock && this.isWithinBoard(startBlock.pos), ) as PatchBlock[] - DBG_HOLES_HIGHLIGHT_COLOR && + WorldConf.debug.boardHolesHighlightColor && startBlockPositions.forEach(block => { const patch = this.findPatch(block.pos) if (patch && block) { - block.data.type = DBG_HOLES_HIGHLIGHT_COLOR + block.data.type = WorldConf.debug.boardHolesHighlightColor block.data.level -= 1 // dig hole in the ground block.data.mode = BlockMode.DEFAULT patch.writeBlockData(block.index, block.data) diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts index 4d29d72..45acb8b 100644 --- a/src/datacontainers/GroundPatchesMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -2,20 +2,19 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' import { asBox2 } from '../common/utils' -import { WorldConfig } from '../config/WorldConfig' -import { BlocksPatch, WorldComputeApi } from '../index' +import { BlocksPatch, WorldComputeApi, WorldConf } from '../index' import { PatchesMap } from './PatchesMap' const getDefaultPatchDim = () => - new Vector2(WorldConfig.patchSize, WorldConfig.patchSize) + new Vector2(WorldConf.patchSize, WorldConf.patchSize) /** * Blocks cache */ export class CacheContainer extends PatchesMap { static cachePowRadius = 2 - static cacheSize = WorldConfig.patchSize * 5 + static cacheSize = WorldConf.patchSize * 5 // eslint-disable-next-line no-use-before-define static singleton: CacheContainer pendingRefresh = false diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 280489a..010d11a 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -4,16 +4,16 @@ import { Box2, Vector2 } from 'three' import { ProcLayer } from '../procgen/ProcLayer' import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' -import { WorldConfig } from '../config/WorldConfig' import { patchLowerId, patchUpperId } from '../common/utils' +import { WorldConf } from '../index' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' const probabilityThreshold = Math.pow(2, 8) const bmin = new Vector2(0, 0) const bmax = new Vector2( - WorldConfig.defaultDistMapPeriod, - WorldConfig.defaultDistMapPeriod, + WorldConf.defaultDistMapPeriod, + WorldConf.defaultDistMapPeriod, ) const distMapDefaultBox = new Box2(bmin, bmax) const distMapDefaults = { diff --git a/src/index.ts b/src/index.ts index 8d0957b..1cba19a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,15 @@ +export { Biome, 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 { BoardContainer } from './datacontainers/BoardMap' -export { Biome, BlockType } from './procgen/Biome' export { EntityType } from './common/types' export { PatchesMap } from './datacontainers/PatchesMap' export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' export { ChunkFactory } from './tools/ChunkFactory' -export { WorldConfig } from './config/WorldConfig' export { WorldComputeApi } from './api/WorldComputeApi' export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts new file mode 100644 index 0000000..157a142 --- /dev/null +++ b/src/misc/WorldConfig.ts @@ -0,0 +1,19 @@ +import { BlockType } from "../index" + +export class WorldConf { + static patchPowSize = 6 // as a power of two + static get patchSize() { + return Math.pow(2, this.patchPowSize) + } + // max cache radius as a power of two + static cachePowLimit = 2 // 4 => 16 patches radius + static get cacheLimit() { + return Math.pow(2, this.cachePowLimit) + } + static defaultDistMapPeriod = 4 * WorldConf.patchSize + static debug = { + patchBordersHighlightColor: BlockType.NONE, + boardStartPosHighlightColor: BlockType.NONE, + boardHolesHighlightColor: BlockType.NONE, + } +} diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 766d875..2b6b1a7 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -3,16 +3,14 @@ import { Box3, MathUtils, Vector2, Vector3 } from 'three' import { EntityData, PatchBlock, PatchId } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockData, BlockMode } from '../datacontainers/BlocksPatch' -import { BlockType } from '../index' +import { BlockType, WorldConf } from '../index' import { TreeGenerators } from './TreeGenerator' -const DBG_BORDERS_HIGHLIGHT_COLOR = BlockType.NONE // use NONE to disable - // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return DBG_BORDERS_HIGHLIGHT_COLOR && (localPos.x === 1 || localPos.z === 1) - ? DBG_BORDERS_HIGHLIGHT_COLOR + return WorldConf.debug.patchBordersHighlightColor && (localPos.x === 1 || localPos.z === 1) + ? WorldConf.debug.patchBordersHighlightColor : blockType } From fa79b7216010c8acdfedcef95133cb5c07b84143 Mon Sep 17 00:00:00 2001 From: etienne Date: Sat, 31 Aug 2024 09:26:41 +0000 Subject: [PATCH 31/45] fix: reduce distribution map queries slowdowns --- src/api/world-compute.ts | 13 +- src/common/utils.ts | 4 +- src/datacontainers/BlocksPatch.ts | 4 +- src/datacontainers/BoardMap.ts | 14 +-- src/datacontainers/DataContainers.ts | 70 ++++++----- src/datacontainers/RandomDistributionMap.ts | 69 ++++------- src/procgen/Biome.ts | 1 + src/procgen/BlueNoisePattern.ts | 131 ++++++++++---------- 8 files changed, 146 insertions(+), 160 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index b99300c..a809afc 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -96,10 +96,8 @@ const genEntity = (entityPos: Vector3) => { export const computeBlocksBuffer = (blockPos: Vector3) => { let blocksBuffer // query entities at current block - const entityShaper = (entityPos: Vector2) => - new Box2().setFromCenterAndSize(entityPos, asVect2(entityDefaultDims)) - const mapPos = asVect2(blockPos) - const spawnLocs = distributionMap.querySpawnLocations(entityShaper, mapPos) + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 + const spawnLocs = distributionMap.querySpawnLocations(asVect2(blockPos), intersectsEntity) for (const loc of spawnLocs) { const entityPos = asVect3(loc) const entity = genEntity(entityPos) @@ -116,11 +114,8 @@ export const computeBlocksBuffer = (blockPos: Vector3) => { const genEntities = (blocksPatch: BlocksPatch) => { // query entities on patch range - const entityDims = new Vector3(10, 20, 10) // TODO compute from entity type - const entityShaper = (entityPos: Vector2) => - new Box2().setFromCenterAndSize(entityPos, asVect2(entityDims)) - const mapBox = asBox2(blocksPatch.bbox) - const spawnLocs = distributionMap.querySpawnLocations(entityShaper, mapBox) + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 + const spawnLocs = distributionMap.querySpawnLocations(asBox2(blocksPatch.bbox), intersectsEntity) const spawnedEntities = spawnLocs .map(loc => asVect3(loc)) .map(entityPos => genEntity(entityPos)) diff --git a/src/common/utils.ts b/src/common/utils.ts index 5c7395b..8f7610e 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -255,7 +255,7 @@ const parsePatchKey = (patchKey: PatchKey) => { return patchId } -const patchLowerId = (position: Vector2, patchSize: Vector2) => { +const getPatchId = (position: Vector2, patchSize: Vector2) => { const patchId = position.clone().divide(patchSize).floor() return patchId } @@ -331,7 +331,7 @@ export { asBox2, asBox3, parsePatchKey, - patchLowerId, + getPatchId, patchUpperId, serializePatchId, patchBoxFromKey, diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index bd52029..2cbe602 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -15,9 +15,9 @@ import { asVect2, asBox3, chunkBoxFromId, - patchLowerId, serializePatchId, asBox2, + getPatchId, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { ChunkFactory, WorldConf } from '../index' @@ -385,7 +385,7 @@ export class BlocksPatch implements GenericPatch { const bbox = parseThreeStub(patchStub.bbox) as Box3 const patchCenter = asVect2(bbox.getCenter(new Vector3())) const patchDim = asVect2(bbox.getSize(new Vector3()).round()) - const patchId = patchLowerId(patchCenter, patchDim) + const patchId = getPatchId(patchCenter, patchDim) const patchKey = patchStub.key || serializePatchId(patchId) const patch = new BlocksPatch(patchKey) patch.rawDataContainer = rawDataContainer diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 24b3a1c..54ddef6 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -121,11 +121,10 @@ export class BoardContainer extends PatchesMap { } genStartPositions() { - const entityShape = (pos: Vector2) => - new Box2(pos, pos.clone().addScalar(2)) + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 2 const spawnLocs = startPosDistMap.querySpawnLocations( - entityShape, this.bbox, + intersectsEntity, () => 1, ) const startBlockPositions = spawnLocs @@ -202,11 +201,10 @@ export class BoardContainer extends PatchesMap { } digHoles() { - const entityShape = (pos: Vector2) => - new Box2(pos, pos.clone().addScalar(2)) + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 2 const spawnLocs = holesDistMap.querySpawnLocations( - entityShape, this.bbox, + intersectsEntity, () => 1, ) const startBlockPositions = spawnLocs @@ -219,7 +217,7 @@ export class BoardContainer extends PatchesMap { .filter( startBlock => startBlock && this.isWithinBoard(startBlock.pos), ) as PatchBlock[] - WorldConf.debug.boardHolesHighlightColor && + WorldConf.debug.boardHolesHighlightColor && startBlockPositions.forEach(block => { const patch = this.findPatch(block.pos) if (patch && block) { @@ -241,5 +239,5 @@ export class BoardContainer extends PatchesMap { // } } - smoothEdges() {} + smoothEdges() { } } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 008ebf8..89af26b 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -3,44 +3,56 @@ */ import { Vector2, Box2, Vector3 } from 'three' - -import { patchLowerId, patchUpperId } from '../common/utils' +import { getPatchId, patchUpperId } from '../common/utils' // GenericPatch export interface GenericPatch { - key: any - bbox: any - chunkIds: any - duplicate(): GenericPatch - toChunks(): any - // toLocalPos(pos: T): T - // toGlobalPos(pos: T): T - toLocalPos(pos: Vector3): Vector3 - toGlobalPos(pos: Vector3): Vector3 - containsPoint(pos: Vector3): Boolean + key: any + bbox: any + chunkIds: any + duplicate(): GenericPatch + toChunks(): any + // toLocalPos(pos: T): T + // toGlobalPos(pos: T): T + toLocalPos(pos: Vector3): Vector3 + toGlobalPos(pos: Vector3): Vector3 + containsPoint(pos: Vector3): Boolean } /** * Generic PatchesMap */ export class GenericPatchesMap { - patchDimensions: Vector2 - constructor(patchDim: Vector2) { - this.patchDimensions = patchDim - } + patchDimensions: Vector2 + constructor(patchDim: Vector2) { + this.patchDimensions = patchDim + } + + getPatchRange(bbox: Box2) { + const rangeMin = getPatchId(bbox.min, this.patchDimensions) + const rangeMax = patchUpperId(bbox.max, this.patchDimensions)//.addScalar(1) + const patchRange = new Box2(rangeMin, rangeMax) + return patchRange + } - getPatchRange(bbox: Box2) { - const rangeMin = patchLowerId(bbox.min, this.patchDimensions) - const rangeMax = patchUpperId(bbox.max, this.patchDimensions) - const patchRange = new Box2(rangeMin, rangeMax) - return patchRange - } + getPatchIds(bbox: Box2) { + const patchIds = [] + const patchRange = this.getPatchRange(bbox) + // iter elements on computed range + const { min, max } = patchRange + for (let { x } = min; x <= max.x; x++) { + for (let { y } = min; y <= max.y; y++) { + patchIds.push(new Vector2(x, y)) + } + } + return patchIds + } - getRoundedBox(bbox: Box2) { - const { min, max } = this.getPatchRange(bbox) - min.multiply(this.patchDimensions) - max.multiply(this.patchDimensions) - const extBbox = new Box2(min, max) - return extBbox - } + getRoundedBox(bbox: Box2) { + const { min, max } = this.getPatchRange(bbox) + min.multiply(this.patchDimensions) + max.multiply(this.patchDimensions) + const extBbox = new Box2(min, max) + return extBbox + } } diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 010d11a..9530814 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -4,8 +4,8 @@ import { Box2, Vector2 } from 'three' import { ProcLayer } from '../procgen/ProcLayer' import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' -import { patchLowerId, patchUpperId } from '../common/utils' import { WorldConf } from '../index' +import { GenericPatchesMap } from './DataContainers' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' @@ -29,7 +29,7 @@ const distMapDefaults = { * Enable querying/iterating randomly distributed items at block * level or from custom box range */ -export class PseudoDistributionMap { +export class PseudoDistributionMap extends GenericPatchesMap { repeatedPattern: BlueNoisePattern densityMap: ProcLayer @@ -37,6 +37,7 @@ export class PseudoDistributionMap { bbox: Box2 = distMapDefaultBox, distParams: any = distMapDefaults, ) { + super(bbox.getSize(new Vector2())) this.repeatedPattern = new BlueNoisePattern(bbox, distParams) this.densityMap = new ProcLayer(distParams.aleaSeed || '') } @@ -62,32 +63,6 @@ export class PseudoDistributionMap { return hasSpawned } - getPatchIdsRange(mapArea: Box2) { - const { dimensions } = this.repeatedPattern - const rangeMin = patchLowerId(mapArea.min, dimensions) - const rangeMax = patchUpperId(mapArea.max, dimensions) - return new Box2(rangeMin, rangeMax) - } - - *iterPatchIds(mapArea: Box2) { - const patchRange = this.getPatchIdsRange(mapArea) - const patchOffset = patchRange.min.clone() - // iter elements on computed range - for ( - patchOffset.x = patchRange.min.x; - patchOffset.x < patchRange.max.x; - patchOffset.x++ - ) { - for ( - patchOffset.y = patchRange.min.y; - patchOffset.y < patchRange.max.y; - patchOffset.y++ - ) { - yield patchOffset - } - } - } - /** * * @param entityShaper @@ -96,28 +71,32 @@ export class PseudoDistributionMap { * @returns all locations from which entity contains input point or overlaps with range box */ querySpawnLocations( - entityShaper: (centerPos: Vector2) => Box2, - inputPointOrArea: Vector2 | Box2, + testRange: Vector2 | Box2, + overlapsTest: (testRange: Box2, entityPos: Vector2) => boolean, spawnProbabilityOverride?: (entityPos?: Vector2) => number, // entityMask = (_entity: EntityData) => false ) { - const mapBox = - inputPointOrArea instanceof Box2 - ? inputPointOrArea - : new Box2().setFromPoints([inputPointOrArea]) + const testBox = + testRange instanceof Box2 + ? testRange + : new Box2().setFromPoints([testRange]) + // 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 = this.iterPatchIds(mapBox) + const patchIds = this.getPatchIds(testBox) for (const patchId of patchIds) { - const patchElements = this.repeatedPattern.iterPatchElements(patchId) - // look for entities overlapping with input point or area - for (const entityPos of patchElements) { - const entityBox = entityShaper(entityPos) - const isOverlappingEntity = - inputPointOrArea instanceof Vector2 - ? entityBox.containsPoint(inputPointOrArea) - : entityBox.intersectsBox(mapBox) - if (isOverlappingEntity) overlappingEntities.push(entityPos) - } + const offset = patchId.clone().multiply(this.patchDimensions) + const localTestBox = testBox.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) + } + } } const spawnedEntities = overlappingEntities.filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos)), diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index 007b9a7..dce1f01 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -29,6 +29,7 @@ export enum BlockType { DBG_DARK, DBG_PURPLE, DBG_ORANGE, + DBG_GREEN, } export enum BiomeType { diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index f1e3bca..ef68da8 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -6,78 +6,79 @@ import { Box2, Vector2 } from 'three' * Self repeating seamless pattern */ export class BlueNoisePattern { - bbox: Box2 - params - elements: Vector2[] = [] + bbox: Box2 + params + elements: Vector2[] = [] - constructor(bbox: Box2, distParams: any) { - this.bbox = bbox - this.params = distParams - this.populate() - } + constructor(bbox: Box2, distParams: any) { + this.bbox = bbox + this.params = distParams + this.populate() + } - get dimensions() { - return this.bbox.getSize(new Vector2()) - } + get dimensions() { + return this.bbox.getSize(new Vector2()) + } - // populate with discrete elements using relative pos - populate() { - const { dimensions, params } = this - const { aleaSeed } = this.params - const prng = alea(aleaSeed || '') - const p = new PoissonDiskSampling( - { - shape: [dimensions.x, dimensions.y], - ...params, - }, - prng, - ) - this.elements = p - .fill() - .map(point => new Vector2(point[0] as number, point[1] as number).round()) - this.makeSeamless() - } + // populate with discrete elements using relative pos + populate() { + const { dimensions, params } = this + const { aleaSeed } = this.params + const prng = alea(aleaSeed || '') + const p = new PoissonDiskSampling( + { + shape: [dimensions.x, dimensions.y], + ...params, + }, + prng, + ) + this.elements = p + .fill() + .map(point => new Vector2(point[0] as number, point[1] as number).round()) + this.makeSeamless() + } - // make seamless repeatable pattern - makeSeamless() { - const { dimensions, params } = this - const radius = params.minDistance / 2 - const edgePoints = this.elements - .map(point => { - const pointCopy = point.clone() - if (point.x - radius < 0) { - pointCopy.x += dimensions.x - } else if (point.x + radius > dimensions.x) { - pointCopy.x -= dimensions.x - } - if (point.y - radius < 0) { - pointCopy.y += dimensions.y - } else if (point.y + radius > dimensions.y) { - pointCopy.y -= dimensions.y - } - return pointCopy.round().equals(point) ? null : pointCopy - }) - .filter(pointCopy => pointCopy) - edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) - } + // make seamless repeatable pattern + makeSeamless() { + const { dimensions, params } = this + const radius = params.minDistance / 2 + const edgePoints = this.elements + .map(point => { + const pointCopy = point.clone() + if (point.x - radius < 0) { + pointCopy.x += dimensions.x + } else if (point.x + radius > dimensions.x) { + pointCopy.x -= dimensions.x + } + if (point.y - radius < 0) { + pointCopy.y += dimensions.y + } else if (point.y + radius > dimensions.y) { + pointCopy.y -= dimensions.y + } + return pointCopy.round().equals(point) ? null : pointCopy + }) + .filter(pointCopy => pointCopy) + edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) + } - getPatchOrigin(patchId: Vector2) { - return patchId.clone().multiply(this.dimensions) - } + getPatchOrigin(patchId: Vector2) { + return patchId.clone().multiply(this.dimensions) + } - toPatchLocalPos(pos: Vector2, patchId: Vector2) { - return pos.clone().sub(this.getPatchOrigin(patchId)) - } + toPatchLocalPos(pos: Vector2, patchId: Vector2) { + return pos.clone().sub(this.getPatchOrigin(patchId)) + } - toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { - return relativePos.clone().add(this.getPatchOrigin(patchId)) - } + toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { + return relativePos.clone().add(this.getPatchOrigin(patchId)) + } - *iterPatchElements(patchOffset: Vector2) { - // relative to global pos conv - for (const relativePos of this.elements) { - const pos = this.toPatchGlobalPos(relativePos, patchOffset) - yield pos + // DO NOT USE SLOW + *iterPatchElements(patchOffset: Vector2) { + // relative to global pos conv + for (const relativePos of this.elements) { + const pos = this.toPatchGlobalPos(relativePos, patchOffset) + yield pos + } } - } } From 330424496c91528191883201e319e27d418cc19a Mon Sep 17 00:00:00 2001 From: etienne Date: Sat, 31 Aug 2024 10:30:55 +0000 Subject: [PATCH 32/45] feat: board data export and code cleanup --- src/datacontainers/BoardMap.ts | 167 +++++++++++++++------------------ 1 file changed, 78 insertions(+), 89 deletions(-) diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 54ddef6..727f4b7 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -1,21 +1,30 @@ -import { Box2, Box3, Vector2, Vector3 } from 'three' +import { Box2, Vector2, Vector3 } from 'three' -import { EntityData, PatchBlock } from '../common/types' +import { Block, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { WorldCacheContainer, WorldConf } from '../index' import { PseudoDistributionMap } from './RandomDistributionMap' -import { BlockData, BlockMode, BlocksPatch } from './BlocksPatch' +import { BlockMode, BlocksPatch } from './BlocksPatch' import { PatchesMap } from './PatchesMap' -export type BoardStub = { - bbox: Box3 - data: BlockData +export type BoardData = { + box: Box2, + groundBlocks: [], + entities: { + startPos: Block[], + holes: Block[], + obstacles: Block[], + } } const getDefaultPatchDim = () => new Vector2(WorldConf.patchSize, WorldConf.patchSize) +/** + * Entities distribution conf + */ +// Start positions const startPosDistParams = { aleaSeed: 'boardStartPos', minDistance: 10, @@ -24,6 +33,7 @@ const startPosDistParams = { } const startPosDistMap = new PseudoDistributionMap(undefined, startPosDistParams) +// Holes const holesDistParams = { aleaSeed: 'boardHoles', minDistance: 10, @@ -47,6 +57,13 @@ export class BoardContainer extends PatchesMap { this.init(this.bbox) } + initBoard(): void { + this.shapeBoard() + this.showStartPositions() + this.digHoles() + this.trimTrees() + } + restoreOriginalPatches() { const original_patches_container = new PatchesMap(getDefaultPatchDim()) original_patches_container.init(this.bbox) @@ -75,6 +92,27 @@ export class BoardContainer extends PatchesMap { return block } + getBoardEntities(distMap: PseudoDistributionMap, entityRadius = 2) { + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= entityRadius + const spawnLocs = distMap.querySpawnLocations( + this.bbox, + intersectsEntity, + () => 1, + ) + const entities = spawnLocs + .map(loc => { + const startPos = asVect3(loc) + const patch = this.findPatch(startPos) + const block = patch?.getBlock(startPos, false) + return block + }) + .filter( + ent => ent && this.isWithinBoard(ent.pos), + ) as PatchBlock[] + // TODO prune entities spawning over existing entities + return entities + } + shapeBoard() { // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) @@ -97,61 +135,36 @@ export class BoardContainer extends PatchesMap { } } - getAllPatchesEntities(skipDuplicate = true) { - const entities: EntityData[] = [] - for (const patch of this.availablePatches) { - patch.entities.forEach(entity => { - if ( - !skipDuplicate || - !entities.find(ent => ent.bbox.equals(entity.bbox)) - ) { - entities.push(entity) + smoothEdges() { } + + showStartPositions() { + const startPositions = this.getBoardEntities(startPosDistMap) + WorldConf.debug.boardStartPosHighlightColor && + startPositions.forEach(block => { + const patch = this.findPatch(block.pos) + if (patch && block) { + block.data.type = WorldConf.debug.boardStartPosHighlightColor + block.data.mode = BlockMode.DEFAULT + patch.writeBlockData(block.index, block.data) + // patch.setBlock(block.pos, block.data) } }) - } - return entities + return startPositions } - getBoardEntities() { - const boardEntities = this.getAllPatchesEntities().filter(ent => { - const entityCenter = ent.bbox.getCenter(new Vector3()) - return this.isWithinBoard(entityCenter) - }) - return boardEntities - } - - genStartPositions() { - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 2 - const spawnLocs = startPosDistMap.querySpawnLocations( - this.bbox, - intersectsEntity, - () => 1, - ) - const startBlockPositions = spawnLocs - .map(loc => { - const startPos = asVect3(loc) - const patch = this.findPatch(startPos) - const block = patch?.getBlock(startPos, false) - return block - }) - .filter( - startBlock => startBlock && this.isWithinBoard(startBlock.pos), - ) as PatchBlock[] - WorldConf.debug.boardStartPosHighlightColor && - startBlockPositions.forEach(block => { + digHoles() { + const holes = this.getBoardEntities(holesDistMap) + WorldConf.debug.boardHolesHighlightColor && + holes.forEach(block => { const patch = this.findPatch(block.pos) if (patch && block) { - block.data.type = WorldConf.debug.boardStartPosHighlightColor + block.data.type = WorldConf.debug.boardHolesHighlightColor + block.data.level -= 1 // dig hole in the ground block.data.mode = BlockMode.DEFAULT patch.writeBlockData(block.index, block.data) // patch.setBlock(block.pos, block.data) } }) - // const existingBoardEntities = this.getBoardEntities() - // discard entities spawning over existing entities - // const discardEntity = (entity: EntityData) => existingBoardEntities - // .find(boardEntity => entity.bbox.intersectsBox(boardEntity.bbox)) - return startBlockPositions } trimTrees() { @@ -200,44 +213,20 @@ export class BoardContainer extends PatchesMap { }) } - digHoles() { - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 2 - const spawnLocs = holesDistMap.querySpawnLocations( - this.bbox, - intersectsEntity, - () => 1, - ) - const startBlockPositions = spawnLocs - .map(loc => { - const startPos = asVect3(loc) - const patch = this.findPatch(startPos) - const block = patch?.getBlock(startPos, false) - return block - }) - .filter( - startBlock => startBlock && this.isWithinBoard(startBlock.pos), - ) as PatchBlock[] - WorldConf.debug.boardHolesHighlightColor && - startBlockPositions.forEach(block => { - const patch = this.findPatch(block.pos) - if (patch && block) { - block.data.type = WorldConf.debug.boardHolesHighlightColor - block.data.level -= 1 // dig hole in the ground - block.data.mode = BlockMode.DEFAULT - patch.writeBlockData(block.index, block.data) - // patch.setBlock(block.pos, block.data) - } - }) - } - - exportBoard() { - // const data = - // const boardData = { - // origin, - // size, - // data - // } + dataExport() { + const startPos = this.getBoardEntities(startPosDistMap) + const holes = this.getBoardEntities(holesDistMap) + // TODO refactor: consider trees as other entities + const obstacles: PatchBlock[] = [] + const boardData: BoardData = { + box: new Box2(), + groundBlocks: [], + entities: { + startPos, + holes, + obstacles, + } + } + return boardData } - - smoothEdges() { } } From e6d48ab162397288679631da93db388c926f3536 Mon Sep 17 00:00:00 2001 From: etienne Date: Sun, 1 Sep 2024 09:11:35 +0000 Subject: [PATCH 33/45] refactor: merge worker api into unique compute api with optional worker instance --- src/api/WorldComputeApi.ts | 138 +++++++++++++-------------------- src/api/world-compute.ts | 62 +++++++-------- src/datacontainers/BoardMap.ts | 41 +++++++--- 3 files changed, 110 insertions(+), 131 deletions(-) diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeApi.ts index f29f575..3edc4cd 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -1,12 +1,11 @@ import { Vector3 } from 'three' -import { Block, PatchKey } from '../common/types' +import { Block, EntityKey, PatchKey } from '../common/types' import { BlocksPatch, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { - PatchCompute = 'computePatch', + PatchCompute = 'bakeGroundPatch', BlocksBatchCompute = 'computeBlocksBatch', - GroundBlockCompute = 'computeGroundBlock', OvergroundBufferCompute = 'computeOvergroundBuffer', } @@ -16,25 +15,14 @@ export type ComputeApiParams = Partial<{ includeEntitiesBlocks: boolean // skip or include entities blocks }> -interface ComputeApiInterface { - computeBlocksBatch( - blockPosBatch: Vector3[], - params?: any, - ): Block[] | Promise - // computePatch(patchKey: PatchKey): BlocksPatch | Promise - iterPatchCompute( - patchKeysBatch: PatchKey[], - ): - | Generator - | AsyncGenerator -} - -export class WorldComputeApi implements ComputeApiInterface { - static singleton: ComputeApiInterface - - pendingTask = false - startTime = Date.now() - elapsedTime = 0 +/** + * All methods exposed here supports worker mode and will forward + * requests to external compute module if worker instance is provided + */ +export class WorldComputeApi { + static singleton: WorldComputeApi + workerInstance: Worker | undefined + resolvers: Record = {} count = 0 static get instance() { @@ -42,91 +30,73 @@ export class WorldComputeApi implements ComputeApiInterface { return this.singleton } - // eslint-disable-next-line no-undef - static useWorker(worker: Worker) { - this.singleton = new WorldComputeProxy(worker) - } - - computeBlocksBatch( - blockPosBatch: Vector3[], - params = { includeEntitiesBlocks: true }, - ) { - return WorldCompute.computeBlocksBatch(blockPosBatch, params) - } - - *iterPatchCompute(patchKeysBatch: PatchKey[]) { - for (const patchKey of patchKeysBatch) { - const patch = WorldCompute.computePatch(patchKey) - yield patch - } + get worker() { + return this.workerInstance } -} - -/** - * Proxying requests to worker instead of internal world compute - */ -export class WorldComputeProxy implements ComputeApiInterface { - // eslint-disable-next-line no-undef - worker: Worker - count = 0 - resolvers: Record = {} - // eslint-disable-next-line no-undef - constructor(worker: Worker) { - // super() - this.worker = worker - this.worker.onmessage = ({ data }) => { + set worker(workerInstance: Worker) { + workerInstance.onmessage = ({ data }) => { if (data.id !== undefined) { this.resolvers[data.id]?.(data.data) delete this.resolvers[data.id] - } else { - if (data) { - // data.kept?.length > 0 && PatchBlocksCache.cleanDeprecated(data.kept) - // data.created?.forEach(blocks_cache => { - // const blocks_patch = new PatchBlocksCache(blocks_cache) - // PatchBlocksCache.instances.push(blocks_patch) - // // patchRenderQueue.push(blocksPatch) - // }) - } } } - this.worker.onerror = error => { + workerInstance.onerror = error => { console.error(error) } - this.worker.onmessageerror = error => { + workerInstance.onmessageerror = error => { console.error(error) } + this.workerInstance = workerInstance } + /** + * Proxying request to worker + */ workerCall(apiName: ComputeApiCall, args: any[]) { - const id = this.count++ - this.worker.postMessage({ id, apiName, args }) - return new Promise(resolve => (this.resolvers[id] = resolve)) + if (this.worker) { + const id = this.count++ + this.worker.postMessage({ id, apiName, args }) + return new Promise(resolve => (this.resolvers[id] = resolve)) + } } - async computeBlocksBatch(blockPosBatch: Vector3[], params?: any) { - const blockStubs = await this.workerCall( - ComputeApiCall.BlocksBatchCompute, - [blockPosBatch, params], - ) - // parse worker's data to recreate original objects - const blocks: Block[] = blockStubs.map((blockStub: Block) => { - blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos) - return blockStub - }) + async computeBlocksBatch( + blockPosBatch: Vector3[], + params = { includeEntitiesBlocks: true }, + ) { + 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[] + return blocks } + // *iterEntitiesBaking(entityKeys: EntityKey[]) { + // for (const entityKey of entityKeys) { + // const entityChunk = WorldCompute.bakeChunkEntity(entityKey) + // yield entityChunk + // } + // } + async *iterPatchCompute(patchKeysBatch: PatchKey[]) { for (const patchKey of patchKeysBatch) { - // const emptyPatch = new BlocksPatch(patchKey) - const patchStub = await this.workerCall( - ComputeApiCall.PatchCompute, - [patchKey], // [emptyPatch.bbox] - ) - const patch = BlocksPatch.fromStub(patchStub) + const patch = !this.worker ? WorldCompute.bakeGroundPatch(patchKey) : + await this.workerCall( + ComputeApiCall.PatchCompute, + [patchKey], // [emptyPatch.bbox] + )?.then(patchStub => BlocksPatch.fromStub(patchStub)) as BlocksPatch + yield patch } } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index a809afc..787bee3 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -12,24 +12,18 @@ const entityDefaultDims = new Vector3(10, 20, 10) // TODO move somewhere else const distributionMap = new PseudoDistributionMap() -export const computePatch = (patchKey: PatchKey) => { - const patch = new BlocksPatch(patchKey) - genGroundBlocks(patch) - genEntities(patch) - return patch -} - export const computeBlocksBatch = ( blockPosBatch: Vector3[], params = { includeEntitiesBlocks: false }, ) => { + const { includeEntitiesBlocks } = params const blocksBatch = blockPosBatch.map(({ x, z }) => { const blockPos = new Vector3(x, 0, z) const blockData = computeGroundBlock(blockPos) - if (params.includeEntitiesBlocks) { - const blocksBuffer = computeBlocksBuffer(blockPos) - const lastBlockIndex = blocksBuffer.findLastIndex(elt => elt) - if (lastBlockIndex >= 0) { + if (includeEntitiesBlocks) { + const blocksBuffer = bakeChunkEntity(blockPos, true)?.data + const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) + if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { blockData.level += lastBlockIndex blockData.type = blocksBuffer[lastBlockIndex] as BlockType } @@ -44,7 +38,7 @@ export const computeBlocksBatch = ( return blocksBatch } -export const computeGroundBlock = (blockPos: Vector3) => { +const computeGroundBlock = (blockPos: Vector3) => { const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) const mainBiome = Biome.instance.getMainBiome(biomeContribs) const rawVal = Heightmap.instance.getRawVal(blockPos) @@ -93,45 +87,43 @@ const genEntity = (entityPos: Vector3) => { return entity } -export const computeBlocksBuffer = (blockPos: Vector3) => { - let blocksBuffer +const genEntities = (blocksPatch: BlocksPatch) => { + // query entities on patch range + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 + const spawnLocs = distributionMap.querySpawnLocations(asBox2(blocksPatch.bbox), intersectsEntity) + const spawnedEntities = spawnLocs + .map(loc => asVect3(loc)) + .map(entityPos => genEntity(entityPos)) + .filter(val => val) as EntityData[] + blocksPatch.entities = spawnedEntities +} + +export const bakeChunkEntity = (blockPos: Vector3, singleBlockOnly = false) => { + let entityChunk // query entities at current block const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 const spawnLocs = distributionMap.querySpawnLocations(asVect2(blockPos), intersectsEntity) for (const loc of spawnLocs) { const entityPos = asVect3(loc) const entity = genEntity(entityPos) - blocksBuffer = entity - ? ChunkFactory.chunkifyEntity(entity, blockPos).data - : blocksBuffer + if (entity) { + entityChunk = singleBlockOnly ? ChunkFactory.chunkifyEntity(entity, blockPos) : + ChunkFactory.chunkifyEntity(entity) + } } - return blocksBuffer || [] + return entityChunk } // export const bakeEntities = (_entities: EntityData) => { // // TODO // } -const genEntities = (blocksPatch: BlocksPatch) => { - // query entities on patch range - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 - const spawnLocs = distributionMap.querySpawnLocations(asBox2(blocksPatch.bbox), intersectsEntity) - const spawnedEntities = spawnLocs - .map(loc => asVect3(loc)) - .map(entityPos => genEntity(entityPos)) - .filter(val => val) as EntityData[] - blocksPatch.entities = spawnedEntities -} - /** - * Fill container with ground blocks + * Fill ground blocks container */ -const genGroundBlocks = (blocksPatch: BlocksPatch) => { +export const bakeGroundPatch = (patchKey: PatchKey) => { + const blocksPatch = new BlocksPatch(patchKey) const { min, max } = blocksPatch.bbox - // const patchId = min.x + ',' + min.z + '-' + max.x + ',' + max.z - // const prng = alea(patchId) - // const refPoints = this.isTransitionPatch ? this.buildRefPoints() : [] - // const blocksPatch = new PatchBlocksCache(new Vector2(min.x, min.z)) const patchBlocks = blocksPatch.iterBlocksQuery(undefined, false) min.y = 512 max.y = 0 diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardMap.ts index 727f4b7..b050476 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardMap.ts @@ -10,7 +10,7 @@ import { PatchesMap } from './PatchesMap' export type BoardData = { box: Box2, - groundBlocks: [], + blocks: [], entities: { startPos: Block[], holes: Block[], @@ -22,7 +22,7 @@ const getDefaultPatchDim = () => new Vector2(WorldConf.patchSize, WorldConf.patchSize) /** - * Entities distribution conf + * Entities distribution default conf */ // Start positions const startPosDistParams = { @@ -31,7 +31,6 @@ const startPosDistParams = { maxDistance: 16, tries: 20, } -const startPosDistMap = new PseudoDistributionMap(undefined, startPosDistParams) // Holes const holesDistParams = { @@ -40,9 +39,16 @@ const holesDistParams = { maxDistance: 16, tries: 20, } -const holesDistMap = new PseudoDistributionMap(undefined, holesDistParams) export class BoardContainer extends PatchesMap { + // static singleton: BoardContainer + // static get instance(){ + // return this.singleton + // } + static holesDistribution = new PseudoDistributionMap(undefined, holesDistParams) + static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) + + // board instance params boardCenter boardRadius boardMaxHeightDiff @@ -59,6 +65,7 @@ export class BoardContainer extends PatchesMap { initBoard(): void { this.shapeBoard() + console.log(this.dataExport()) this.showStartPositions() this.digHoles() this.trimTrees() @@ -138,7 +145,7 @@ export class BoardContainer extends PatchesMap { smoothEdges() { } showStartPositions() { - const startPositions = this.getBoardEntities(startPosDistMap) + const startPositions = this.getBoardEntities(BoardContainer.startPosDistribution) WorldConf.debug.boardStartPosHighlightColor && startPositions.forEach(block => { const patch = this.findPatch(block.pos) @@ -153,7 +160,7 @@ export class BoardContainer extends PatchesMap { } digHoles() { - const holes = this.getBoardEntities(holesDistMap) + const holes = this.getBoardEntities(BoardContainer.holesDistribution) WorldConf.debug.boardHolesHighlightColor && holes.forEach(block => { const patch = this.findPatch(block.pos) @@ -214,13 +221,23 @@ export class BoardContainer extends PatchesMap { } dataExport() { - const startPos = this.getBoardEntities(startPosDistMap) - const holes = this.getBoardEntities(holesDistMap) - // TODO refactor: consider trees as other entities - const obstacles: PatchBlock[] = [] + const {startPosDistribution, holesDistribution} = BoardContainer + const startPos = this.getBoardEntities(startPosDistribution) + .map(block => ({ + pos: block.pos, + data: block.data + })) + const holes = this.getBoardEntities(holesDistribution) + .map(block => ({ + pos: block.pos, + data: block.data + })) + // TODO refactor: consider trees as no longer attached to ground patch + // but retrievable like any other entities + const obstacles: Block[] = [] const boardData: BoardData = { - box: new Box2(), - groundBlocks: [], + box: this.bbox, + blocks: [], entities: { startPos, holes, From 726f035514398a4f90abe04ea99339db8bd362db Mon Sep 17 00:00:00 2001 From: etienne Date: Sun, 1 Sep 2024 21:56:28 +0000 Subject: [PATCH 34/45] refactor: bake entities separately from ground patch + misc other changes --- src/api/WorldComputeApi.ts | 25 +- src/api/world-compute.ts | 170 ++++++------ src/common/types.ts | 8 +- src/common/utils.ts | 51 +++- src/datacontainers/BlocksPatch.ts | 254 +++++++----------- .../{BoardMap.ts => BoardContainer.ts} | 46 ++-- src/datacontainers/DataContainers.ts | 116 +++++++- src/datacontainers/EntityChunkMaker.ts | 81 ++++++ src/datacontainers/GroundPatch.ts | 24 ++ src/datacontainers/GroundPatchesMap.ts | 36 ++- src/index.ts | 3 +- src/procgen/WorldEntities.ts | 58 ++++ src/tools/ChunkFactory.ts | 63 +---- 13 files changed, 587 insertions(+), 348 deletions(-) rename src/datacontainers/{BoardMap.ts => BoardContainer.ts} (89%) create mode 100644 src/datacontainers/EntityChunkMaker.ts create mode 100644 src/datacontainers/GroundPatch.ts create mode 100644 src/procgen/WorldEntities.ts diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeApi.ts index 3edc4cd..46f5b42 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeApi.ts @@ -1,12 +1,14 @@ -import { Vector3 } from 'three' +import { Box2, Vector3 } from 'three' import { Block, EntityKey, PatchKey } from '../common/types' +import { EntityChunk } from '../datacontainers/EntityChunkMaker' import { BlocksPatch, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { PatchCompute = 'bakeGroundPatch', BlocksBatchCompute = 'computeBlocksBatch', OvergroundBufferCompute = 'computeOvergroundBuffer', + BakeEntities = 'bakeEntities' } export type ComputeApiParams = Partial<{ @@ -65,7 +67,7 @@ export class WorldComputeApi { async computeBlocksBatch( blockPosBatch: Vector3[], - params = { includeEntitiesBlocks: true }, + params = { includeEntitiesBlocks: false }, ) { const blocks = !this.worker ? WorldCompute.computeBlocksBatch(blockPosBatch, params) : @@ -100,4 +102,23 @@ export class WorldComputeApi { yield patch } } + + async bakeEntities(queriedRange: Box2,) { + const entityChunks = !this.worker ? + WorldCompute.bakeEntities(queriedRange) : + await this.workerCall( + ComputeApiCall.BakeEntities, + [queriedRange], + )?.then((entityChunks: EntityChunk[]) => + // parse worker's data to recreate original objects + entityChunks.map(chunkStub => { + chunkStub.box = WorldUtils.parseThreeStub(chunkStub.box) + if (chunkStub.entity) { + chunkStub.entity.bbox = WorldUtils.parseThreeStub(chunkStub.entity?.bbox) + } + return chunkStub + })) as EntityChunk[] + + return entityChunks + } } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 787bee3..d238fe8 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,17 +1,23 @@ -import { Box2, Box3, Vector2, Vector3 } from 'three' +import { Box2, Vector2, Vector3 } from 'three' -import { ChunkFactory, EntityType, PseudoDistributionMap } from '../index' +import { BoardContainer, EntityType, GroundPatch } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { BlockData, BlocksPatch } from '../datacontainers/BlocksPatch' +import { BlockData } from '../datacontainers/BlocksPatch' import { Block, EntityData, PatchKey } from '../common/types' -import { asBox2, asVect2, asVect3 } from '../common/utils' - -// TODO remove hardcoded entity dimensions to compute from entity type -const entityDefaultDims = new Vector3(10, 20, 10) -// TODO move somewhere else -const distributionMap = new PseudoDistributionMap() +import { asBox3, asVect2, asVect3 } from '../common/utils' +import { WorldEntities } from '../procgen/WorldEntities' +import { EntityChunk, EntityChunkMaker } from '../datacontainers/EntityChunkMaker' +/** + * BLOCKS + */ +/** + * + * @param blockPosBatch + * @param params + * @returns + */ export const computeBlocksBatch = ( blockPosBatch: Vector3[], params = { includeEntitiesBlocks: false }, @@ -21,7 +27,18 @@ export const computeBlocksBatch = ( const blockPos = new Vector3(x, 0, z) const blockData = computeGroundBlock(blockPos) if (includeEntitiesBlocks) { - const blocksBuffer = bakeChunkEntity(blockPos, true)?.data + const entityRange = new Box2().setFromPoints([asVect2(blockPos)]) + entityRange.max.addScalar(1) + const foundEntity = queryEntities(entityRange) + .map(entityData => new EntityChunkMaker(entityData))[0] + let blocksBuffer + if (foundEntity) { + const { min, max } = foundEntity.entityData.bbox + const voxelizationRange = asBox3(entityRange) + voxelizationRange.min.y = min.y + voxelizationRange.max.y = max.y + blocksBuffer = foundEntity.voxelizeEntity(voxelizationRange) + } const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { blockData.level += lastBlockIndex @@ -38,7 +55,7 @@ export const computeBlocksBatch = ( return blocksBatch } -const computeGroundBlock = (blockPos: Vector3) => { +export const computeGroundBlock = (blockPos: Vector3) => { const biomeContribs = Biome.instance.getBiomeInfluence(blockPos) const mainBiome = Biome.instance.getMainBiome(biomeContribs) const rawVal = Heightmap.instance.getRawVal(blockPos) @@ -60,87 +77,80 @@ const computeGroundBlock = (blockPos: Vector3) => { return block } -const genEntity = (entityPos: Vector3) => { - let entity: EntityData | undefined +/** + * PATCHES + */ + +export const bakeGroundPatch = (patchKeyOrBox: PatchKey | Box2) => { + const groundPatch = new GroundPatch(patchKeyOrBox) + groundPatch.fill() + return groundPatch +} + +export const computeBoardData = (center: Vector3, radius: number, maxThickness: number) => { + const boardMap = new BoardContainer(center, radius, maxThickness) + const boardGroundBlocks = bakeGroundPatch(boardMap.bbox) +} + +/** + * ENTITIES + */ + +export const bakeEntities = (queriedRange: Box2) => { + const entitiesData = queryEntities(queriedRange) + return bakeEntitiesBatch(entitiesData) +} + +export const bakeEntitiesBatch = (entities: EntityData[]) => { + const entitiesChunks: EntityChunk[] = entities + .map(entityData => new EntityChunkMaker(entityData)) + .map(entityChunkMaker => { + entityChunkMaker.voxelizeEntity() + return entityChunkMaker.toStub() as EntityChunk + }) + return entitiesChunks +} + + +/** + * + * @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 mainBiome = Biome.instance.getMainBiome(entityPos) const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome) const entityType = blockTypes.entities?.[0] as EntityType + // confirm this kind of entity can spawn over here if (entityType) { - entityPos.y = - Heightmap.instance.getGroundLevel(entityPos, rawVal) + - entityDefaultDims.y / 2 - const entityBox = new Box3().setFromCenterAndSize( - entityPos, - entityDefaultDims, - ) - entity = { - type: entityType, - bbox: entityBox, - params: { - radius: 5, - size: 10, - }, - } + entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos, rawVal) + entity.bbox.max.y += entity.bbox.min.y + return entity } - return entity -} - -const genEntities = (blocksPatch: BlocksPatch) => { - // query entities on patch range - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 - const spawnLocs = distributionMap.querySpawnLocations(asBox2(blocksPatch.bbox), intersectsEntity) - const spawnedEntities = spawnLocs - .map(loc => asVect3(loc)) - .map(entityPos => genEntity(entityPos)) - .filter(val => val) as EntityData[] - blocksPatch.entities = spawnedEntities + return } -export const bakeChunkEntity = (blockPos: Vector3, singleBlockOnly = false) => { - let entityChunk - // query entities at current block - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= 5 - const spawnLocs = distributionMap.querySpawnLocations(asVect2(blockPos), intersectsEntity) - for (const loc of spawnLocs) { - const entityPos = asVect3(loc) - const entity = genEntity(entityPos) - if (entity) { - entityChunk = singleBlockOnly ? ChunkFactory.chunkifyEntity(entity, blockPos) : - ChunkFactory.chunkifyEntity(entity) - } - } - return entityChunk +const queryEntities = (region: Box2 | Vector2) => { + const spawnablePlaces = WorldEntities.instance.queryDistributionMap(EntityType.TREE_APPLE)(region) + const spawnedEntities = spawnablePlaces + .map(entLoc => WorldEntities.instance.getEntityData(EntityType.TREE_PINE, asVect3(entLoc))) + .filter(entity => confirmFinalizeEntity(entity)) + return spawnedEntities } -// export const bakeEntities = (_entities: EntityData) => { +// /** +// * return all entity types which can spwawn over specific region +// */ +// const getSpawnableEntities = (region: Box2) => { // // TODO // } -/** - * Fill ground blocks container - */ -export const bakeGroundPatch = (patchKey: PatchKey) => { - const blocksPatch = new BlocksPatch(patchKey) - const { min, max } = blocksPatch.bbox - const patchBlocks = blocksPatch.iterBlocksQuery(undefined, false) - min.y = 512 - max.y = 0 - let blockIndex = 0 - for (const block of patchBlocks) { - // const patchCorner = points.find(pt => pt.distanceTo(blockData.pos) < 2) - const blockData = computeGroundBlock(block.pos) - min.y = Math.min(min.y, blockData.level) - max.y = Math.max(max.y, blockData.level) - blocksPatch.writeBlockData(blockIndex, blockData) - blockIndex++ - } - blocksPatch.bbox.min = min - blocksPatch.bbox.max = max - blocksPatch.bbox.getSize(blocksPatch.dimensions) - // PatchBlocksCache.bbox.union(blocksPatch.bbox) +// /** +// * granular check in transition place (spline or biome transitions) +// */ +// const confirmSpawnability = () => { - // blocksPatch.state = PatchState.Filled - return blocksPatch -} +// } diff --git a/src/common/types.ts b/src/common/types.ts index 054ca60..d72792a 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -119,18 +119,20 @@ export type EntityData = { type: EntityType bbox: Box3 params: { - radius: 5 - size: 10 + radius: number + size: number } } +export type EntityKey = string + export type PatchKey = string export type PatchId = Vector2 export type ChunkKey = string export type ChunkId = Vector3 export type ChunkDataContainer = { - bbox: Box3 + box: Box3 data: Uint16Array } diff --git a/src/common/utils.ts b/src/common/utils.ts index 8f7610e..aec9933 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,4 +1,4 @@ -import { Box2, Box3, Vector2, Vector3, Vector3Like } from 'three' +import { Box2, Box3, Vector2, Vector2Like, Vector3, Vector3Like } from 'three' import { WorldConf } from '../index' import { @@ -216,6 +216,24 @@ const asBox3 = (box2: Box2) => { return new Box3(asVect3(box2.min), asVect3(box2.max)) } +const isVect2Stub = (stub: Vector2Like) => { + return ( + stub !== undefined && + stub.x !== undefined && + stub.y !== undefined && + stub.z === undefined + ) +} + +const isVect3Stub = (stub: Vector3Like) => { + return ( + stub !== undefined && + stub.x !== undefined && + stub.y !== undefined && + stub.z !== undefined + ) +} + const parseVect3Stub = (stub: Vector3Like) => { let res if (isVect3Stub(stub)) { @@ -224,6 +242,24 @@ const parseVect3Stub = (stub: Vector3Like) => { return res } +const parseVect2Stub = (stub: Vector2Like) => { + let res + if (isVect2Stub(stub)) { + res = new Vector2(...Object.values(stub)) + } + return res +} + +const parseBox2Stub = (stub: Box2) => { + let res + if (isVect2Stub(stub.min) && isVect2Stub(stub.max)) { + const min = parseVect2Stub(stub.min) + const max = parseVect2Stub(stub.max) + res = new Box2(min, max) + } + return res +} + const parseBox3Stub = (stub: Box3) => { let res if (isVect3Stub(stub.min) && isVect3Stub(stub.max)) { @@ -234,17 +270,10 @@ const parseBox3Stub = (stub: Box3) => { return res } -const isVect3Stub = (stub: Vector3Like) => { - return ( - stub !== undefined && - stub.x !== undefined && - stub.y !== undefined && - stub.z !== undefined - ) -} - const parseThreeStub = (stub: any) => { - return stub ? parseBox3Stub(stub) || parseVect3Stub(stub) || stub : stub + return stub ? parseBox3Stub(stub) || parseVect3Stub(stub) + || parseBox2Stub(stub) || parseVect2Stub(stub) + || stub : stub } const parsePatchKey = (patchKey: PatchKey) => { diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 2cbe602..83b1054 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -1,4 +1,4 @@ -import { Box3, Vector2, Vector3 } from 'three' +import { Box2, Box3, Vector2, Vector3 } from 'three' import { Block, @@ -12,17 +12,15 @@ import { parsePatchKey, parseThreeStub, serializeChunkId, - asVect2, - asBox3, chunkBoxFromId, - serializePatchId, asBox2, - getPatchId, + asVect3, + asVect2, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { ChunkFactory, WorldConf } from '../index' -import { GenericPatch } from './DataContainers' +import { DataContainer } from './DataContainers' export enum BlockMode { DEFAULT, @@ -37,7 +35,7 @@ export type BlockData = { export type PatchStub = { key: string - bbox: Box3 + bounds: Box3 rawDataContainer: Uint32Array entities: EntityData[] } @@ -55,47 +53,67 @@ export type BlockIteratorRes = IteratorResult const getDefaultPatchDim = () => new Vector2(WorldConf.patchSize, WorldConf.patchSize) +const parseBoundsOrKeyInput = (patchBoundsOrKey: Box2 | string) => { + const bounds = patchBoundsOrKey instanceof Box2 + ? patchBoundsOrKey.clone() + : patchBoxFromKey(patchBoundsOrKey, getDefaultPatchDim()) + return bounds +} + /** * GenericBlocksContainer * multi purpose blocks container */ -export class BlocksPatch implements GenericPatch { - bbox: Box3 - dimensions = new Vector3() - margin = 0 - +export class BlocksPatch extends DataContainer { rawDataContainer: Uint32Array - entities: EntityData[] = [] + margin = 0 key: string | null id: Vector2 | null - constructor(patchBoxOrKey: Box3 | string, margin = 1) { - this.bbox = - patchBoxOrKey instanceof Box3 - ? patchBoxOrKey.clone() - : asBox3(patchBoxFromKey(patchBoxOrKey, getDefaultPatchDim())) - this.key = patchBoxOrKey instanceof Box3 ? null : patchBoxOrKey + constructor(patchBoundsOrKey: Box2 | string, margin = 1) { + super(parseBoundsOrKeyInput(patchBoundsOrKey)) + this.key = typeof patchBoundsOrKey === "string" ? patchBoundsOrKey : null this.id = this.key ? parsePatchKey(this.key) : null - this.bbox.getSize(this.dimensions) this.margin = margin - const { extendedDims } = this - this.rawDataContainer = new Uint32Array(extendedDims.x * extendedDims.z) + this.rawDataContainer = new Uint32Array(this.extendedDims.x * this.extendedDims.y) } - duplicate() { - const copy = new BlocksPatch(this.key || this.bbox) // new BlocksPatch(this.bbox) - this.rawDataContainer.forEach( - (rawVal, i) => (copy.rawDataContainer[i] = rawVal), - ) - copy.entities = this.entities.map(entity => { - const entityCopy: EntityData = { - ...entity, - bbox: entity.bbox.clone(), - } - return entityCopy - }) - return copy as GenericPatch + get extendedBox() { + return this.bounds.clone().expandByScalar(this.margin) + } + + get extendedDims() { + return this.extendedBox.getSize(new Vector2()) + } + + get localBox() { + const localBox = new Box2(new Vector2(0), this.dimensions.clone()) + return localBox + } + + get localExtendedBox() { + return this.localBox.expandByScalar(this.margin) + } + + /** + * @param targetBox if unspecified will be whole source container + */ + copyContentOverTarget(targetBox: Box2) { + const source = this + const targetInput = targetBox || source.bounds + const target = new BlocksPatch(targetInput) + super.copyContentOverTargetContainer(target) + } + + static fromStub(patchOrStub: BlocksPatch) { + const bounds = parseThreeStub(patchOrStub.bounds) as Box2 + const patch = new BlocksPatch(patchOrStub.key || bounds) + patchOrStub.rawDataContainer.forEach( + (rawVal, i) => patch.rawDataContainer[i] = rawVal) + patch.bounds.min.y = patchOrStub.bounds.min.y + patch.bounds.max.y = patchOrStub.bounds.max.y + return patch } decodeBlockData(rawData: number): BlockData { @@ -131,61 +149,25 @@ export class BlocksPatch implements GenericPatch { this.rawDataContainer[blockIndex] = this.encodeBlockData(blockData) } - get extendedBox() { - return this.bbox.clone().expandByScalar(this.margin) - } - - get extendedDims() { - return this.extendedBox.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) - } - - isWithinLocalRange(localPos: Vector3) { - return ( - localPos.x >= 0 && - localPos.x < this.dimensions.x && - localPos.z >= 0 && - localPos.z < this.dimensions.z - ) - } - - isWithinGlobalRange(globalPos: Vector3) { - return ( - globalPos.x >= this.bbox.min.x && - globalPos.x < this.bbox.max.x && - globalPos.z >= this.bbox.min.z && - globalPos.z < this.bbox.max.z - ) - } - - adjustRangeBox(rangeBox: Box3 | Vector3, local = false) { + adjustRangeBox(rangeBox: Box2 | Vector2, local = false) { rangeBox = - rangeBox instanceof Box3 ? rangeBox : new Box3(rangeBox, rangeBox) - const { min, max } = local ? this.localBox : this.bbox - const rangeMin = new Vector3( + rangeBox instanceof Box2 ? rangeBox : new Box2(rangeBox, rangeBox) + const { min, max } = local ? this.localBox : this.bounds + const rangeMin = new Vector2( Math.max(Math.floor(rangeBox.min.x), min.x), - 0, - Math.max(Math.floor(rangeBox.min.z), min.z), + Math.max(Math.floor(rangeBox.min.y), min.y), ) - const rangeMax = new Vector3( + const rangeMax = new Vector2( Math.min(Math.floor(rangeBox.max.x), max.x), - 0, - Math.min(Math.floor(rangeBox.max.z), max.z), + Math.min(Math.floor(rangeBox.max.y), max.y), ) return local - ? new Box3(rangeMin, rangeMax) - : new Box3(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) + ? new Box2(rangeMin, rangeMax) + : new Box2(asVect2(this.toLocalPos(asVect3(rangeMin))), + asVect2(this.toLocalPos(asVect3(rangeMax)))) } - getBlockIndex(localPos: Vector3) { + override getIndex(localPos: Vector3) { return ( (localPos.x + this.margin) * this.extendedDims.x + localPos.z + @@ -193,27 +175,15 @@ export class BlocksPatch implements GenericPatch { ) } - toLocalPos(pos: Vector3) { - const origin = this.bbox.min.clone() - origin.y = 0 - return pos.clone().sub(origin) - } - - toGlobalPos(pos: Vector3) { - const origin = this.bbox.min.clone() - origin.y = 0 - return origin.add(pos) - } - getBlock(inputPos: Vector3, isLocalPos = true) { const isWithingRange = isLocalPos - ? this.isWithinLocalRange(inputPos) - : this.isWithinGlobalRange(inputPos) + ? this.inLocalRange(inputPos) + : this.inGlobalRange(inputPos) let block: PatchBlock | undefined if (isWithingRange) { const localPos = isLocalPos ? inputPos : this.toLocalPos(inputPos) const pos = isLocalPos ? this.toGlobalPos(inputPos) : inputPos - const blockIndex = this.getBlockIndex(localPos) + const blockIndex = this.getIndex(localPos) const blockData = this.readBlockData(blockIndex) || BlockType.NONE localPos.y = blockData.level pos.y = blockData.level @@ -229,16 +199,16 @@ export class BlocksPatch implements GenericPatch { setBlock(pos: Vector3, blockData: BlockData, isLocalPos = false) { const isWithingPatch = isLocalPos - ? this.isWithinLocalRange(pos) - : this.isWithinGlobalRange(pos) + ? this.inLocalRange(pos) + : this.inGlobalRange(pos) if (isWithingPatch) { const localPos = isLocalPos ? pos : this.toLocalPos(pos) - const blockIndex = this.getBlockIndex(localPos) + const blockIndex = this.getIndex(localPos) this.writeBlockData(blockIndex, blockData) } // const levelMax = blockLevel + blockData.over.length - // bbox.min.y = Math.min(bbox.min.y, levelMax) - // bbox.max.y = Math.max(bbox.max.y, levelMax) + // bounds.min.y = Math.min(bounds.min.y, levelMax) + // bounds.max.y = Math.max(bounds.max.y, levelMax) } getBlocksRow(zRowIndex: number) { @@ -257,7 +227,7 @@ export class BlocksPatch implements GenericPatch { * @param rangeBox iteration range as global coords * @param skipMargin */ - *iterBlocksQuery(rangeBox?: Box3 | Vector3, skipMargin = true) { + *iterBlocksQuery(rangeBox?: Box2 | Vector2, skipMargin = true) { // convert to local coords to speed up iteration const localBbox = rangeBox ? this.adjustRangeBox(rangeBox) @@ -268,15 +238,15 @@ export class BlocksPatch implements GenericPatch { this.margin > 0 && (x === localBbox.min.x || x === localBbox.max.x - 1 || - z === localBbox.min.z || - z === localBbox.max.z - 1) + z === localBbox.min.y || + z === localBbox.max.y - 1) let index = 0 for (let { x } = localBbox.min; x < localBbox.max.x; x++) { - for (let { z } = localBbox.min; z < localBbox.max.z; z++) { - const localPos = new Vector3(x, 0, z) + for (let { y } = localBbox.min; y < localBbox.max.y; y++) { + const localPos = new Vector3(x, 0, y) if (!skipMargin || !isMarginBlock(localPos)) { - index = rangeBox ? this.getBlockIndex(localPos) : index + index = rangeBox ? this.getIndex(localPos) : index const blockData = this.readBlockData(index) || BlockType.NONE localPos.y = blockData.level const block: PatchBlock = { @@ -292,29 +262,19 @@ export class BlocksPatch implements GenericPatch { } } - containsPoint(blockPos: Vector3) { - return asBox2(this.bbox).containsPoint(asVect2(blockPos)) - // return ( - // blockPos.x >= this.bbox.min.x && - // blockPos.z >= this.bbox.min.z && - // blockPos.x < this.bbox.max.x && - // blockPos.z < this.bbox.max.z - // ) - } - *iterEntityChunkBlocks(entityChunk: ChunkDataContainer) { // return overlapping blocks between entity and container - const entityDims = entityChunk.bbox.getSize(new Vector3()) - const blocks = this.iterBlocksQuery(entityChunk.bbox) + const entityDims = entityChunk.box.getSize(new Vector3()) + const blocks = this.iterBlocksQuery(asBox2(entityChunk.box)) for (const block of blocks) { // const buffer = entityChunk.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) - const chunkLocalPos = block.pos.clone().sub(entityChunk.bbox.min) + const chunkLocalPos = block.pos.clone().sub(entityChunk.box.min) const buffIndex = chunkLocalPos.z * entityDims.x * entityDims.y + chunkLocalPos.x * entityDims.y block.buffer = entityChunk.data.slice(buffIndex, buffIndex + entityDims.y) - const buffOffset = entityChunk.bbox.min.y - block.pos.y + const buffOffset = entityChunk.box.min.y - block.pos.y const buffSrc = Math.abs(Math.min(0, buffOffset)) const buffDest = Math.max(buffOffset, 0) block.buffer = block.buffer?.copyWithin(buffDest, buffSrc) @@ -330,33 +290,33 @@ export class BlocksPatch implements GenericPatch { // multi-pass chunk filling toChunk(chunkBox: Box3) { let totalWrittenBlocks = 0 - chunkBox = chunkBox || this.bbox + chunkBox = chunkBox || this.bounds const chunkDims = chunkBox.getSize(new Vector3()) const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) // Ground pass const groundBlocksIterator = this.iterBlocksQuery(undefined, false) // ground blocks pass - totalWrittenBlocks += ChunkFactory.default.fillGroundData( + totalWrittenBlocks += ChunkFactory.default.voxelizeGround( groundBlocksIterator, chunkData, chunkBox, ) // Entities pass - for (const entity of this.entities) { - // const entityChunk = this.buildEntityChunk(entity) - const entityChunk = ChunkFactory.chunkifyEntity(entity) - const entityDataIterator = this.iterEntityChunkBlocks(entityChunk) // this.iterEntityBlocks(entity) - totalWrittenBlocks += ChunkFactory.default.mergeEntitiesData( - entityDataIterator, - chunkData, - chunkBox, - ) - } + // for (const entity of this.entities) { + // // const entityChunk = this.buildEntityChunk(entity) + // const entityChunk = ChunkFactory.chunkifyEntity(entity) + // const entityDataIterator = this.iterEntityChunkBlocks(entityChunk) // this.iterEntityBlocks(entity) + // totalWrittenBlocks += ChunkFactory.default.mergeEntitiesData( + // entityDataIterator, + // chunkData, + // chunkBox, + // ) + // } // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) // const dimensions = new Vector3(size, size, size) const chunk = { - bbox: chunkBox, + bounds: chunkBox, data: totalWrittenBlocks ? chunkData : null, // isEmpty: totalWrittenBlocks === 0, } @@ -380,24 +340,10 @@ export class BlocksPatch implements GenericPatch { return chunks } - static fromStub(patchStub: any) { - const { rawDataContainer, entities } = patchStub - const bbox = parseThreeStub(patchStub.bbox) as Box3 - const patchCenter = asVect2(bbox.getCenter(new Vector3())) - const patchDim = asVect2(bbox.getSize(new Vector3()).round()) - const patchId = getPatchId(patchCenter, patchDim) - const patchKey = patchStub.key || serializePatchId(patchId) - const patch = new BlocksPatch(patchKey) - patch.rawDataContainer = rawDataContainer - patch.entities = entities.map((stub: EntityData) => ({ - ...stub, - bbox: parseThreeStub(stub.bbox), - })) - patch.bbox.min.y = patchStub.bbox.min.y - patch.bbox.max.y = patchStub.bbox.max.y - // patchStub.entitiesChunks?.forEach((entityChunk: EntityChunk) => - // patch.entitiesChunks.push(entityChunk), - // ) - return patch + /** + * Split container into fixed size patches + */ + asSplittedPatchMap() { + } } diff --git a/src/datacontainers/BoardMap.ts b/src/datacontainers/BoardContainer.ts similarity index 89% rename from src/datacontainers/BoardMap.ts rename to src/datacontainers/BoardContainer.ts index b050476..0ae8c5c 100644 --- a/src/datacontainers/BoardMap.ts +++ b/src/datacontainers/BoardContainer.ts @@ -40,13 +40,15 @@ const holesDistParams = { tries: 20, } -export class BoardContainer extends PatchesMap { +// const initBoardBox = () + +export class BoardContainer extends BlocksPatch { // static singleton: BoardContainer // static get instance(){ // return this.singleton // } static holesDistribution = new PseudoDistributionMap(undefined, holesDistParams) - static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) + static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) // board instance params boardCenter @@ -54,7 +56,7 @@ export class BoardContainer extends PatchesMap { boardMaxHeightDiff constructor(center: Vector3, radius: number, maxHeightDiff: number) { - super(getDefaultPatchDim()) + super(new Box2()) this.boardRadius = radius this.boardCenter = center.clone().floor() this.boardMaxHeightDiff = maxHeightDiff @@ -120,28 +122,36 @@ export class BoardContainer extends PatchesMap { return entities } - shapeBoard() { - // const { ymin, ymax } = this.getMinMax() - // const avg = Math.round(ymin + (ymax - ymin) / 2) - // reset bbox to refine bounds - this.bbox.min = asVect2(this.boardCenter) - this.bbox.max = asVect2(this.boardCenter) - + *iterBoardBlock() { for (const patch of this.availablePatches) { const blocks = patch.iterBlocksQuery(undefined, false) // const blocks = this.iterPatchesBlocks() for (const block of blocks) { - // discard blocs not included in board shape + // discard blocks not included in board shape if (this.isWithinBoard(block.pos)) { - const boardBlock = this.overrideBlock(block) - patch.writeBlockData(boardBlock.index, boardBlock.data) - this.bbox.expandByPoint(asVect2(boardBlock.pos)) - // yield boardBlock + yield block } } } } + shapeBoard() { + // const { ymin, ymax } = this.getMinMax() + // const avg = Math.round(ymin + (ymax - ymin) / 2) + // reset bbox to refine bounds + const blocksContainer = new BlocksPatch(this.bbox, 0) + const adjustedBox = new Box2(asVect2(this.boardCenter), asVect2(this.boardCenter)) + + const boardBlocks = this.iterBoardBlock() + for (const block of boardBlocks) { + const boardBlock = this.overrideBlock(block) + blocksContainer.setBlock(boardBlock.pos, boardBlock.data, false) + adjustedBox.expandByPoint(asVect2(boardBlock.pos)) + // yield boardBlock + } + const finalBlocksContainer = new BlocksPatch(adjustedBox) + } + smoothEdges() { } showStartPositions() { @@ -220,8 +230,12 @@ export class BoardContainer extends PatchesMap { }) } + copyBoardData() { + + } + dataExport() { - const {startPosDistribution, holesDistribution} = BoardContainer + const { startPosDistribution, holesDistribution } = BoardContainer const startPos = this.getBoardEntities(startPosDistribution) .map(block => ({ pos: block.pos, diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 89af26b..58bd713 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -3,20 +3,110 @@ */ import { Vector2, Box2, Vector3 } from 'three' -import { getPatchId, patchUpperId } from '../common/utils' +import { ChunkId } from '../common/types' +import { asVect2, asVect3, getPatchId, patchUpperId } from '../common/utils' + +// export enum BitLength { +// Uint16, +// Uint32 +// } + +// const getArrayConstructor = (bitLength: BitLength) => { +// switch (bitLength) { +// case BitLength.Uint16: +// return Uint16Array +// case BitLength.Uint32: +// return Uint32Array +// } +// } // GenericPatch -export interface GenericPatch { - key: any - bbox: any - chunkIds: any - duplicate(): GenericPatch - toChunks(): any +export abstract class DataContainer { + bounds: Box2 + dimensions: Vector2 + abstract rawDataContainer: T + + constructor(bounds: Box2) {//, bitLength = BitLength.Uint16) { + this.bounds = bounds + this.dimensions = bounds.getSize(new Vector2()) + // this.rawDataContainer = getArrayConstructor(bitLength) + } + + /** + * @param target target container to copy data to + */ + copyContentOverTargetContainer(target: DataContainer) { + const source = this + // const targetInput = targetBox || source.bounds + // const target = new DataContainer(targetInput) + const localMin = target.bounds.min.clone().sub(source.bounds.min) + const localMax = target.bounds.max.clone().sub(source.bounds.max) + const rowLength = localMax.x - localMin.x + const rowStartPos = localMin.clone() + const sourceContainer = source.rawDataContainer + const targetContainer = target.rawDataContainer + let targetIndex = 0 + while (rowStartPos.y < localMax.y) + for (let yRowIndex = localMin.y; yRowIndex < localMax.y; yRowIndex++) { + // const startIndex = this.getBlockIndex(new Vector2(zRowIndex, )) + let remaining = rowLength + let sourceIndex = this.getIndex(asVect3(rowStartPos)) + while (remaining--) { + const rawVal = sourceContainer[sourceIndex] || 0 + targetContainer[targetIndex] = rawVal + sourceIndex++ + targetIndex++ + } + rowStartPos.z++ + } + } + inLocalRange(localPos: Vector3 | Vector2) { + localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) + return ( + localPos.x >= 0 && + localPos.x < this.dimensions.x && + localPos.y >= 0 && + localPos.y < this.dimensions.y + ) + } + + inGlobalRange(globalPos: Vector3 | Vector2) { + globalPos = globalPos instanceof Vector2 ? globalPos : asVect2(globalPos) + 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 + ) + } + + getIndex(localPos: Vector3) { + return localPos.x * this.dimensions.x + localPos.z + } + // toLocalPos(pos: T): T // toGlobalPos(pos: T): T - toLocalPos(pos: Vector3): Vector3 - toGlobalPos(pos: Vector3): Vector3 - containsPoint(pos: Vector3): Boolean + + toLocalPos(pos: Vector3) { + const origin = asVect3(this.bounds.min.clone()) + return pos.clone().sub(origin) + } + + toGlobalPos(pos: Vector3) { + const origin = asVect3(this.bounds.min.clone()) + return origin.add(pos) + } + containsPoint(pos: Vector3) { + return this.bounds.containsPoint(asVect2(pos)) + // return ( + // blockPos.x >= this.bounds.min.x && + // blockPos.z >= this.bounds.min.z && + // blockPos.x < this.bounds.max.x && + // blockPos.z < this.bounds.max.z + // ) + } + abstract get chunkIds(): ChunkId[] + abstract toChunks(): any } /** @@ -55,4 +145,10 @@ export class GenericPatchesMap { const extBbox = new Box2(min, max) return extBbox } + /** + * Merges all patches as single data container + */ + asMergedContainer() { + + } } diff --git a/src/datacontainers/EntityChunkMaker.ts b/src/datacontainers/EntityChunkMaker.ts new file mode 100644 index 0000000..79c08c6 --- /dev/null +++ b/src/datacontainers/EntityChunkMaker.ts @@ -0,0 +1,81 @@ +import { Box3, Vector3, Vector2 } from "three" +import { EntityData } from "../common/types" +import { asVect2 } from "../common/utils" +import { BlockType } from "../index" +import { TreeGenerators } from "../tools/TreeGenerator" + +export type EntityChunk = { + box: Box3, + data: Uint16Array, + entity?: EntityData +} + +export class EntityChunkMaker { + entityData: EntityData + chunkBox: Box3 = new Box3().setFromPoints([new Vector3()]) + chunkData: Uint16Array | undefined + + constructor(entityData: EntityData) { + this.entityData = entityData + } + + voxelizeEntity(chunkBox?: Vector3 | Box3) { + const { bbox, params, type } = this.entityData + if (chunkBox instanceof Vector3) { + const blockStart = new Vector3( + chunkBox.x, + bbox.min.y, + chunkBox.z, + ) + const blockEnd = blockStart + .clone() + .add(new Vector3(1, bbox.max.y - bbox.min.y, 1)) + chunkBox = new Box3(blockStart, blockEnd) + } + this.chunkBox = chunkBox || bbox + const dims = this.chunkBox.getSize(new Vector3()) + this.chunkData = new Uint16Array(dims.z * dims.x * dims.y) + 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 + } + + toStub() { + const { chunkBox, chunkData, entityData } = this + if (chunkData) { + const entityChunk: EntityChunk = { + box: chunkBox, + data: chunkData, + entity: entityData + } + return entityChunk + } + return + } +} \ No newline at end of file diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts new file mode 100644 index 0000000..00321d7 --- /dev/null +++ b/src/datacontainers/GroundPatch.ts @@ -0,0 +1,24 @@ +import { computeGroundBlock } from "../api/world-compute"; +import { BlocksPatch } from "./BlocksPatch"; + +export class GroundPatch extends BlocksPatch { + fill() { + const { min, max } = this.bounds + const blocks = this.iterBlocksQuery(undefined, false) + const level = { + min: 512, + max: 0 + } + let blockIndex = 0 + for (const block of blocks) { + const blockData = computeGroundBlock(block.pos) + level.min = Math.min(min.y, blockData.level) + level.max = Math.max(max.y, blockData.level) + this.writeBlockData(blockIndex, blockData) + blockIndex++ + } + // this.bounds.min = min + // this.bounds.max = max + // this.bounds.getSize(this.dimensions) + } +} \ No newline at end of file diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts index 45acb8b..71e861f 100644 --- a/src/datacontainers/GroundPatchesMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -1,7 +1,7 @@ -import { Box2, Box3, Vector2, Vector3 } from 'three' +import { Box2, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' -import { asBox2 } from '../common/utils' +import { asVect3 } from '../common/utils' import { BlocksPatch, WorldComputeApi, WorldConf } from '../index' import { PatchesMap } from './PatchesMap' @@ -32,7 +32,7 @@ export class CacheContainer extends PatchesMap { for await (const patch of batchIter) { if (patch.key) { this.patchLookup[patch.key] = patch - this.bbox.union(asBox2(patch.bbox)) + this.bbox.union(patch.bounds) } } this.pendingRefresh = false @@ -68,27 +68,23 @@ export class CacheContainer extends PatchesMap { return changesDiff } - getPatches(inputBbox: Box3) { - const bbox = inputBbox.clone() - bbox.min.y = 0 - bbox.max.y = 512 - const res = this.availablePatches.filter(patch => - patch.bbox.intersectsBox(bbox), + getPatches(inputBox: Box2) { + return this.availablePatches.filter(patch => + patch.bounds.intersectsBox(inputBox), ) - return res } getNearPatches(patch: BlocksPatch) { const dim = patch.dimensions - const patchCenter = patch.bbox.getCenter(new Vector3()) - const minX = patchCenter.clone().add(new Vector3(-dim.x, 0, 0)) - const maxX = patchCenter.clone().add(new Vector3(dim.x, 0, 0)) - const minZ = patchCenter.clone().add(new Vector3(0, 0, -dim.z)) - const maxZ = patchCenter.clone().add(new Vector3(0, 0, dim.z)) - const minXminZ = patchCenter.clone().add(new Vector3(-dim.x, 0, -dim.z)) - const minXmaxZ = patchCenter.clone().add(new Vector3(-dim.x, 0, dim.z)) - const maxXminZ = patchCenter.clone().add(new Vector3(dim.x, 0, -dim.z)) - const maxXmaxZ = patchCenter.clone().add(new Vector3(dim.x, 0, dim.z)) + const patchCenter = patch.bounds.getCenter(new Vector2()) + const minX = patchCenter.clone().add(new Vector3(-dim.x, 0)) + const maxX = patchCenter.clone().add(new Vector3(dim.x, 0)) + const minZ = patchCenter.clone().add(new Vector3(0, -dim.y)) + const maxZ = patchCenter.clone().add(new Vector3(0, dim.y)) + const minXminZ = patchCenter.clone().add(new Vector3(-dim.x, -dim.y)) + const minXmaxZ = patchCenter.clone().add(new Vector3(-dim.x, dim.y)) + const maxXminZ = patchCenter.clone().add(new Vector3(dim.x, -dim.y)) + const maxXmaxZ = patchCenter.clone().add(new Vector3(dim.x, dim.y)) const neighboursCenters = [ minX, maxX, @@ -100,7 +96,7 @@ export class CacheContainer extends PatchesMap { maxXmaxZ, ] const patchNeighbours: BlocksPatch[] = neighboursCenters - .map(patchCenter => this.findPatch(patchCenter)) + .map(patchCenter => this.findPatch(asVect3(patchCenter))) .filter(patch => patch) as BlocksPatch[] return patchNeighbours } diff --git a/src/index.ts b/src/index.ts index 1cba19a..58d9976 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,10 +4,11 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' -export { BoardContainer } from './datacontainers/BoardMap' +export { BoardContainer } from './datacontainers/BoardContainer' export { EntityType } from './common/types' export { PatchesMap } from './datacontainers/PatchesMap' export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' +export { GroundPatch } from './datacontainers/GroundPatch' export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' export { ChunkFactory } from './tools/ChunkFactory' export { WorldComputeApi } from './api/WorldComputeApi' diff --git a/src/procgen/WorldEntities.ts b/src/procgen/WorldEntities.ts new file mode 100644 index 0000000..eccd123 --- /dev/null +++ b/src/procgen/WorldEntities.ts @@ -0,0 +1,58 @@ +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) + +export class WorldEntities { + 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) + } +} \ No newline at end of file diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 2b6b1a7..271d691 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -85,7 +85,7 @@ export class ChunkFactory { return written_blocks_count } - fillGroundData( + voxelizeGround( blockIterator: Generator, chunkDataContainer: Uint16Array, chunkBox: Box3, @@ -111,7 +111,7 @@ export class ChunkFactory { return written_blocks_count } - mergeEntitiesData( + mergeEntityChunkData( entityDataIterator: Generator, chunkData: Uint16Array, chunkBox: Box3, @@ -136,55 +136,6 @@ export class ChunkFactory { return writtenBlocksCount } - static chunkifyEntity(entity: EntityData, blockPosOrRange?: Vector3 | Box3) { - if (blockPosOrRange instanceof Vector3) { - const blockStart = new Vector3( - blockPosOrRange.x, - entity.bbox.min.y, - blockPosOrRange.z, - ) - const blockEnd = blockStart - .clone() - .add(new Vector3(1, entity.bbox.max.y - entity.bbox.min.y, 1)) - blockPosOrRange = new Box3(blockStart, blockEnd) - } - const range = blockPosOrRange || entity.bbox - const dims = range.getSize(new Vector3()) - const data = new Uint16Array(dims.z * dims.x * dims.y) - const { size: treeSize, radius: treeRadius } = entity.params - const entityPos = entity.bbox.getCenter(new Vector3()) - let index = 0 - for (let { z } = range.min; z < range.max.z; z++) { - for (let { x } = range.min; x < range.max.x; x++) { - for (let { y } = range.min; y < range.max.y; y++) { - const xzProj = new Vector2(x, z).sub(asVect2(entityPos)) - if (xzProj.length() > 0) { - if (y < range.min.y + treeSize) { - // empty space around trunk between ground and trunk top - data[index++] = BlockType.NONE - } else { - // tree foliage - const blockType = TreeGenerators[entity.type]( - xzProj.length(), - y - (range.min.y + treeSize + treeRadius), - treeRadius, - ) - data[index++] = blockType - } - } else { - // tree trunk - data[index++] = BlockType.TREE_TRUNK - } - } - } - } - const entityChunk = { - bbox: range, - data, - } - return entityChunk - } - genChunksIdsFromPatchId(patchId: PatchId) { const { ymin, ymax } = this.chunksRange const chunk_ids = [] @@ -194,4 +145,14 @@ export class ChunkFactory { } return chunk_ids } + + /** + * Assembles world building blocks (GroundPatch, ChunkEntity) together + * to form final world chunk + */ + chunksAssemby(groundPatches:Ground) { + // chunkify ground patches + // retrieve and chunkify entities + // merge entities chunks with ground chunk + } } From f1432a997f9fb65bf8f8f1803ed706bd20aef2ee Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 4 Sep 2024 08:16:40 +0000 Subject: [PATCH 35/45] refactor: chunkification + misc --- ...orldComputeApi.ts => WorldComputeProxy.ts} | 71 +++++---- src/api/world-compute.ts | 84 +++++----- src/common/types.ts | 10 -- src/common/utils.ts | 20 ++- src/datacontainers/BlocksPatch.ts | 109 ++----------- src/datacontainers/DataContainers.ts | 20 ++- src/datacontainers/EntityChunk.ts | 101 ++++++++++++ src/datacontainers/EntityChunkMaker.ts | 81 ---------- src/datacontainers/GroundPatch.ts | 55 ++++++- src/datacontainers/GroundPatchesMap.ts | 10 +- src/datacontainers/PatchesMap.ts | 4 +- src/datacontainers/RandomDistributionMap.ts | 4 +- src/datacontainers/WorldChunk.ts | 73 +++++++++ src/index.ts | 4 +- src/tools/ChunkFactory.ts | 145 +++--------------- 15 files changed, 383 insertions(+), 408 deletions(-) rename src/api/{WorldComputeApi.ts => WorldComputeProxy.ts} (59%) create mode 100644 src/datacontainers/EntityChunk.ts delete mode 100644 src/datacontainers/EntityChunkMaker.ts create mode 100644 src/datacontainers/WorldChunk.ts diff --git a/src/api/WorldComputeApi.ts b/src/api/WorldComputeProxy.ts similarity index 59% rename from src/api/WorldComputeApi.ts rename to src/api/WorldComputeProxy.ts index 46f5b42..847d214 100644 --- a/src/api/WorldComputeApi.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,14 +1,16 @@ import { Box2, Vector3 } from 'three' import { Block, EntityKey, PatchKey } from '../common/types' -import { EntityChunk } from '../datacontainers/EntityChunkMaker' -import { BlocksPatch, WorldCompute, WorldUtils } from '../index' +import { BoardContainer, BoardParams } from '../datacontainers/BoardContainer' +import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' +import { BlocksPatch, GroundPatch, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { PatchCompute = 'bakeGroundPatch', BlocksBatchCompute = 'computeBlocksBatch', OvergroundBufferCompute = 'computeOvergroundBuffer', - BakeEntities = 'bakeEntities' + BakeEntities = 'queryBakeEntities', + BattleBoardCompute = 'computeBoardData' } export type ComputeApiParams = Partial<{ @@ -18,17 +20,17 @@ export type ComputeApiParams = Partial<{ }> /** - * All methods exposed here supports worker mode and will forward - * requests to external compute module if worker instance is provided + * Exposing world compute api with ability to run inside optional worker + * When provided all request are proxied to worker instead of main thread */ -export class WorldComputeApi { - static singleton: WorldComputeApi +export class WorldComputeProxy { + static singleton: WorldComputeProxy workerInstance: Worker | undefined resolvers: Record = {} count = 0 static get instance() { - this.singleton = this.singleton || new WorldComputeApi() + this.singleton = this.singleton || new WorldComputeProxy() return this.singleton } @@ -36,22 +38,24 @@ export class WorldComputeApi { return this.workerInstance } - set worker(workerInstance: Worker) { - workerInstance.onmessage = ({ data }) => { - if (data.id !== undefined) { - this.resolvers[data.id]?.(data.data) - delete this.resolvers[data.id] + set worker(workerInstance: Worker | undefined) { + this.workerInstance = workerInstance + if (workerInstance) { + workerInstance.onmessage = ({ data }) => { + if (data.id !== undefined) { + this.resolvers[data.id]?.(data.data) + delete this.resolvers[data.id] + } } - } - workerInstance.onerror = error => { - console.error(error) - } + workerInstance.onerror = error => { + console.error(error) + } - workerInstance.onmessageerror = error => { - console.error(error) + workerInstance.onmessageerror = error => { + console.error(error) + } } - this.workerInstance = workerInstance } /** @@ -63,6 +67,7 @@ export class WorldComputeApi { this.worker.postMessage({ id, apiName, args }) return new Promise(resolve => (this.resolvers[id] = resolve)) } + return } async computeBlocksBatch( @@ -97,7 +102,7 @@ export class WorldComputeApi { await this.workerCall( ComputeApiCall.PatchCompute, [patchKey], // [emptyPatch.bbox] - )?.then(patchStub => BlocksPatch.fromStub(patchStub)) as BlocksPatch + )?.then(patchStub => new GroundPatch().fromStub(patchStub)) as GroundPatch yield patch } @@ -105,20 +110,24 @@ export class WorldComputeApi { async bakeEntities(queriedRange: Box2,) { const entityChunks = !this.worker ? - WorldCompute.bakeEntities(queriedRange) : + WorldCompute.queryBakeEntities(queriedRange) : await this.workerCall( ComputeApiCall.BakeEntities, [queriedRange], - )?.then((entityChunks: EntityChunk[]) => + )?.then((entityChunks: EntityChunkStub[]) => // parse worker's data to recreate original objects - entityChunks.map(chunkStub => { - chunkStub.box = WorldUtils.parseThreeStub(chunkStub.box) - if (chunkStub.entity) { - chunkStub.entity.bbox = WorldUtils.parseThreeStub(chunkStub.entity?.bbox) - } - return chunkStub - })) as EntityChunk[] - + entityChunks.map(chunkStub => EntityChunk.fromStub(chunkStub))) return entityChunks } + + async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams) { + const boardData = !this.worker ? + WorldCompute.computeBoardData(boardCenter, boardParams) : + await this.workerCall( + ComputeApiCall.BattleBoardCompute, + [boardCenter, boardParams], + ) + const board = new BoardContainer().fromStub(boardData) + return board + } } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index d238fe8..19c052f 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -7,9 +7,11 @@ import { BlockData } from '../datacontainers/BlocksPatch' import { Block, EntityData, PatchKey } from '../common/types' import { asBox3, asVect2, asVect3 } from '../common/utils' import { WorldEntities } from '../procgen/WorldEntities' -import { EntityChunk, EntityChunkMaker } from '../datacontainers/EntityChunkMaker' +import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' +import { BoardParams } from '../datacontainers/BoardContainer' + /** - * BLOCKS + * Individual blocks requests */ /** @@ -30,15 +32,14 @@ export const computeBlocksBatch = ( const entityRange = new Box2().setFromPoints([asVect2(blockPos)]) entityRange.max.addScalar(1) const foundEntity = queryEntities(entityRange) - .map(entityData => new EntityChunkMaker(entityData))[0] - let blocksBuffer - if (foundEntity) { - const { min, max } = foundEntity.entityData.bbox - const voxelizationRange = asBox3(entityRange) - voxelizationRange.min.y = min.y - voxelizationRange.max.y = max.y - blocksBuffer = foundEntity.voxelizeEntity(voxelizationRange) - } + .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) + })[0] + const blocksBuffer = foundEntity?.voxelize() const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { blockData.level += lastBlockIndex @@ -78,40 +79,38 @@ export const computeGroundBlock = (blockPos: Vector3) => { } /** - * PATCHES + * Patch requests */ +// Ground export const bakeGroundPatch = (patchKeyOrBox: PatchKey | Box2) => { const groundPatch = new GroundPatch(patchKeyOrBox) groundPatch.fill() return groundPatch } -export const computeBoardData = (center: Vector3, radius: number, maxThickness: number) => { - const boardMap = new BoardContainer(center, radius, maxThickness) - const boardGroundBlocks = bakeGroundPatch(boardMap.bbox) +// Battle board +export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams) => { + const { radius, maxThickness } = boardParams + const boardMap = new BoardContainer(boardPos, radius, maxThickness) + boardMap.fill() + boardMap.buildBoard() + const boardData = boardMap.rawDataExport() + return boardData } /** - * ENTITIES + * Entity queries */ -export const bakeEntities = (queriedRange: Box2) => { - const entitiesData = queryEntities(queriedRange) - return bakeEntitiesBatch(entitiesData) -} - -export const bakeEntitiesBatch = (entities: EntityData[]) => { - const entitiesChunks: EntityChunk[] = entities - .map(entityData => new EntityChunkMaker(entityData)) - .map(entityChunkMaker => { - entityChunkMaker.voxelizeEntity() - return entityChunkMaker.toStub() as EntityChunk - }) - return entitiesChunks +export const queryEntities = (region: Box2 | Vector2) => { + const spawnablePlaces = WorldEntities.instance.queryDistributionMap(EntityType.TREE_APPLE)(region) + const spawnedEntities = spawnablePlaces + .map(entLoc => WorldEntities.instance.getEntityData(EntityType.TREE_PINE, asVect3(entLoc))) + .filter(entity => confirmFinalizeEntity(entity)) + return spawnedEntities } - /** * * @param entityPos @@ -133,12 +132,23 @@ const confirmFinalizeEntity = (entity: EntityData) => { return } -const queryEntities = (region: Box2 | Vector2) => { - const spawnablePlaces = WorldEntities.instance.queryDistributionMap(EntityType.TREE_APPLE)(region) - const spawnedEntities = spawnablePlaces - .map(entLoc => WorldEntities.instance.getEntityData(EntityType.TREE_PINE, asVect3(entLoc))) - .filter(entity => confirmFinalizeEntity(entity)) - return spawnedEntities +/** + * Entities baking + */ + +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 } // /** @@ -153,4 +163,4 @@ const queryEntities = (region: Box2 | Vector2) => { // */ // const confirmSpawnability = () => { -// } +// } \ No newline at end of file diff --git a/src/common/types.ts b/src/common/types.ts index d72792a..6a74b0d 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -130,13 +130,3 @@ export type PatchKey = string export type PatchId = Vector2 export type ChunkKey = string export type ChunkId = Vector3 - -export type ChunkDataContainer = { - box: Box3 - data: Uint16Array -} - -export type WorldChunk = { - key: ChunkKey - data: Uint16Array | null -} diff --git a/src/common/utils.ts b/src/common/utils.ts index aec9933..c5b5ffe 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -277,10 +277,13 @@ const parseThreeStub = (stub: any) => { } const parsePatchKey = (patchKey: PatchKey) => { - const patchId = new Vector2( - parseInt(patchKey.split(':')[0] as string), - parseInt(patchKey.split(':')[1] as string), - ) + let patchId + if (patchKey?.length > 0) { + patchId = new Vector2( + parseInt(patchKey.split(':')[0] as string), + parseInt(patchKey.split(':')[1] as string), + ) + } return patchId } @@ -294,9 +297,12 @@ const patchUpperId = (position: Vector2, patchSize: Vector2) => { return patchId } -const serializePatchId = (patchId: PatchId) => { - const { x, y } = patchId - const patchKey = `${x}:${y}` +const serializePatchId = (patchId: PatchId | undefined) => { + let patchKey = '' + if (patchId) { + const { x, y } = patchId + patchKey = `${x}:${y}` + } return patchKey } diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 83b1054..91a02d7 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -3,22 +3,17 @@ import { Box2, Box3, Vector2, Vector3 } from 'three' import { Block, PatchBlock, - WorldChunk, - ChunkDataContainer, EntityData, } from '../common/types' import { patchBoxFromKey, parsePatchKey, parseThreeStub, - serializeChunkId, - chunkBoxFromId, - asBox2, asVect3, asVect2, } from '../common/utils' import { BlockType } from '../procgen/Biome' -import { ChunkFactory, WorldConf } from '../index' +import { WorldConf } from '../index' import { DataContainer } from './DataContainers' @@ -60,10 +55,6 @@ const parseBoundsOrKeyInput = (patchBoundsOrKey: Box2 | string) => { return bounds } -/** - * GenericBlocksContainer - * multi purpose blocks container - */ export class BlocksPatch extends DataContainer { rawDataContainer: Uint32Array margin = 0 @@ -211,17 +202,6 @@ export class BlocksPatch extends DataContainer { // bounds.max.y = Math.max(bounds.max.y, levelMax) } - getBlocksRow(zRowIndex: number) { - const rowStart = zRowIndex * this.dimensions.z - const rowEnd = rowStart + this.dimensions.x - const rowRawData = this.rawDataContainer.slice(rowStart, rowEnd) - return rowRawData - } - - // getBlocksCol(xColIndex: number) { - - // } - /** * * @param rangeBox iteration range as global coords @@ -262,88 +242,21 @@ export class BlocksPatch extends DataContainer { } } - *iterEntityChunkBlocks(entityChunk: ChunkDataContainer) { - // return overlapping blocks between entity and container - const entityDims = entityChunk.box.getSize(new Vector3()) - const blocks = this.iterBlocksQuery(asBox2(entityChunk.box)) - - for (const block of blocks) { - // const buffer = entityChunk.data.slice(chunkBufferIndex, chunkBufferIndex + entityDims.y) - const chunkLocalPos = block.pos.clone().sub(entityChunk.box.min) - const buffIndex = - chunkLocalPos.z * entityDims.x * entityDims.y + - chunkLocalPos.x * entityDims.y - block.buffer = entityChunk.data.slice(buffIndex, buffIndex + entityDims.y) - const buffOffset = entityChunk.box.min.y - block.pos.y - const buffSrc = Math.abs(Math.min(0, buffOffset)) - const buffDest = Math.max(buffOffset, 0) - block.buffer = block.buffer?.copyWithin(buffDest, buffSrc) - block.buffer = - buffOffset < 0 - ? block.buffer?.fill(BlockType.NONE, buffOffset) - : block.buffer - // block.buffer = new Array(20).fill(BlockType.TREE_TRUNK) - yield block - } - } - - // multi-pass chunk filling - toChunk(chunkBox: Box3) { - let totalWrittenBlocks = 0 - chunkBox = chunkBox || this.bounds - const chunkDims = chunkBox.getSize(new Vector3()) - const chunkData = new Uint16Array(chunkDims.x * chunkDims.y * chunkDims.z) - // Ground pass - const groundBlocksIterator = this.iterBlocksQuery(undefined, false) - // ground blocks pass - totalWrittenBlocks += ChunkFactory.default.voxelizeGround( - groundBlocksIterator, - chunkData, - chunkBox, - ) - // Entities pass - // for (const entity of this.entities) { - // // const entityChunk = this.buildEntityChunk(entity) - // const entityChunk = ChunkFactory.chunkifyEntity(entity) - // const entityDataIterator = this.iterEntityChunkBlocks(entityChunk) // this.iterEntityBlocks(entity) - // totalWrittenBlocks += ChunkFactory.default.mergeEntitiesData( - // entityDataIterator, - // chunkData, - // chunkBox, - // ) - // } - - // const size = Math.round(Math.pow(chunk.data.length, 1 / 3)) - // const dimensions = new Vector3(size, size, size) - const chunk = { - bounds: chunkBox, - data: totalWrittenBlocks ? chunkData : null, - // isEmpty: totalWrittenBlocks === 0, - } - return chunk - } + // getBlocksRow(zRowIndex: number) { + // const rowStart = zRowIndex * this.dimensions.y + // const rowEnd = rowStart + this.dimensions.x + // const rowRawData = this.rawDataContainer.slice(rowStart, rowEnd) + // return rowRawData + // } - get chunkIds() { - return this.id ? ChunkFactory.default.genChunksIdsFromPatchId(this.id) : [] - } + // getBlocksCol(xColIndex: number) { - toChunks() { - const chunks = this.chunkIds.map(chunkId => { - const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) - const chunk = this.toChunk(chunkBox) - const worldChunk: WorldChunk = { - key: serializeChunkId(chunkId), - data: chunk.data, - } - return worldChunk - }) - return chunks - } + // } /** * Split container into fixed size patches */ - asSplittedPatchMap() { + // splitAsPatchMap() { - } + // } } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 58bd713..2db036a 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -1,9 +1,4 @@ -/** - * Generic patch data container - */ - import { Vector2, Box2, Vector3 } from 'three' -import { ChunkId } from '../common/types' import { asVect2, asVect3, getPatchId, patchUpperId } from '../common/utils' // export enum BitLength { @@ -20,7 +15,10 @@ import { asVect2, asVect3, getPatchId, patchUpperId } from '../common/utils' // } // } -// GenericPatch + +/** + * Multi purpose data container + */ export abstract class DataContainer { bounds: Box2 dimensions: Vector2 @@ -57,7 +55,7 @@ export abstract class DataContainer { sourceIndex++ targetIndex++ } - rowStartPos.z++ + rowStartPos.y++ } } inLocalRange(localPos: Vector3 | Vector2) { @@ -105,14 +103,14 @@ export abstract class DataContainer { // blockPos.z < this.bounds.max.z // ) } - abstract get chunkIds(): ChunkId[] - abstract toChunks(): any + // abstract get chunkIds(): ChunkId[] + // abstract toChunks(): any } /** - * Generic PatchesMap + * PatchesMap base class */ -export class GenericPatchesMap { +export class PatchesMapBase { patchDimensions: Vector2 constructor(patchDim: Vector2) { this.patchDimensions = patchDim diff --git a/src/datacontainers/EntityChunk.ts b/src/datacontainers/EntityChunk.ts new file mode 100644 index 0000000..6f95423 --- /dev/null +++ b/src/datacontainers/EntityChunk.ts @@ -0,0 +1,101 @@ +import { Box3, Vector3, Vector2, Box2 } from "three" +import { EntityData, PatchBlock } from "../common/types" +import { asBox2, 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 + } +} \ No newline at end of file diff --git a/src/datacontainers/EntityChunkMaker.ts b/src/datacontainers/EntityChunkMaker.ts deleted file mode 100644 index 79c08c6..0000000 --- a/src/datacontainers/EntityChunkMaker.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Box3, Vector3, Vector2 } from "three" -import { EntityData } from "../common/types" -import { asVect2 } from "../common/utils" -import { BlockType } from "../index" -import { TreeGenerators } from "../tools/TreeGenerator" - -export type EntityChunk = { - box: Box3, - data: Uint16Array, - entity?: EntityData -} - -export class EntityChunkMaker { - entityData: EntityData - chunkBox: Box3 = new Box3().setFromPoints([new Vector3()]) - chunkData: Uint16Array | undefined - - constructor(entityData: EntityData) { - this.entityData = entityData - } - - voxelizeEntity(chunkBox?: Vector3 | Box3) { - const { bbox, params, type } = this.entityData - if (chunkBox instanceof Vector3) { - const blockStart = new Vector3( - chunkBox.x, - bbox.min.y, - chunkBox.z, - ) - const blockEnd = blockStart - .clone() - .add(new Vector3(1, bbox.max.y - bbox.min.y, 1)) - chunkBox = new Box3(blockStart, blockEnd) - } - this.chunkBox = chunkBox || bbox - const dims = this.chunkBox.getSize(new Vector3()) - this.chunkData = new Uint16Array(dims.z * dims.x * dims.y) - 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 - } - - toStub() { - const { chunkBox, chunkData, entityData } = this - if (chunkData) { - const entityChunk: EntityChunk = { - box: chunkBox, - data: chunkData, - entity: entityData - } - return entityChunk - } - return - } -} \ No newline at end of file diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index 00321d7..0864ac1 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -1,5 +1,16 @@ -import { computeGroundBlock } from "../api/world-compute"; +import { Vector3 } from "three"; +import { asBox2 } from "../common/utils"; +import { BlockType, WorldCompute, WorldConf } from "../index"; import { BlocksPatch } from "./BlocksPatch"; +import { EntityChunk } from "./EntityChunk"; +import { WorldChunk } from "./WorldChunk"; + +// for debug use only +const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { + return WorldConf.debug.patchBordersHighlightColor && (localPos.x === 1 || localPos.z === 1) + ? WorldConf.debug.patchBordersHighlightColor + : blockType +} export class GroundPatch extends BlocksPatch { fill() { @@ -11,7 +22,7 @@ export class GroundPatch extends BlocksPatch { } let blockIndex = 0 for (const block of blocks) { - const blockData = computeGroundBlock(block.pos) + const blockData = WorldCompute.computeGroundBlock(block.pos) level.min = Math.min(min.y, blockData.level) level.max = Math.max(max.y, blockData.level) this.writeBlockData(blockIndex, blockData) @@ -21,4 +32,44 @@ export class GroundPatch extends BlocksPatch { // this.bounds.max = max // this.bounds.getSize(this.dimensions) } + + fillChunk(worldChunk: WorldChunk) { + const blocks = this.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 || []) + } + } + + mergeEntityVoxels(entityChunk: EntityChunk, worldChunk: WorldChunk) { + // return overlapping blocks between entity and container + const patchBlocksIter = this.iterBlocksQuery(asBox2(entityChunk.chunkBox)) + // 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 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.writeBlock( + block.localPos, + block.data, + bufferData, + ) + } + } } \ No newline at end of file diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts index 71e861f..db2d772 100644 --- a/src/datacontainers/GroundPatchesMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -2,7 +2,7 @@ import { Box2, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' import { asVect3 } from '../common/utils' -import { BlocksPatch, WorldComputeApi, WorldConf } from '../index' +import { BlocksPatch, WorldComputeProxy, WorldConf } from '../index' import { PatchesMap } from './PatchesMap' @@ -27,7 +27,7 @@ export class CacheContainer extends PatchesMap { async populate(batch: PatchKey[]) { this.pendingRefresh = true - const batchIter = WorldComputeApi.instance.iterPatchCompute(batch) + const batchIter = WorldComputeProxy.instance.iterPatchCompute(batch) // populate cache without blocking execution for await (const patch of batchIter) { if (patch.key) { @@ -68,9 +68,11 @@ export class CacheContainer extends PatchesMap { return changesDiff } - getPatches(inputBox: Box2) { + getOverlappingPatches(inputBounds: Box2) { + const overlappingBounds = (bounds1: Box2, bounds2: Box2) => + !(bounds1.max.x <= bounds2.min.x || bounds1.min.x >= bounds2.max.x || bounds1.max.y <= bounds2.min.y || bounds1.min.y >= bounds2.max.y); return this.availablePatches.filter(patch => - patch.bounds.intersectsBox(inputBox), + overlappingBounds(patch.bounds, inputBounds), ) } diff --git a/src/datacontainers/PatchesMap.ts b/src/datacontainers/PatchesMap.ts index f169615..4e4ac3a 100644 --- a/src/datacontainers/PatchesMap.ts +++ b/src/datacontainers/PatchesMap.ts @@ -2,12 +2,12 @@ import { Box2, Vector3 } from 'three' import { PatchKey } from '../common/types' -import { GenericPatch, GenericPatchesMap } from './DataContainers' +import { DataContainer, PatchesMapBase } from './DataContainers' /** * Finite map made from patch aggregation */ -export class PatchesMap extends GenericPatchesMap { +export class PatchesMap> extends PatchesMapBase { bbox: Box2 = new Box2() patchLookup: Record = {} diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 9530814..1f0887b 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -5,7 +5,7 @@ import { ProcLayer } from '../procgen/ProcLayer' import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' import { WorldConf } from '../index' -import { GenericPatchesMap } from './DataContainers' +import { PatchesMapBase } from './DataContainers' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' @@ -29,7 +29,7 @@ const distMapDefaults = { * Enable querying/iterating randomly distributed items at block * level or from custom box range */ -export class PseudoDistributionMap extends GenericPatchesMap { +export class PseudoDistributionMap extends PatchesMapBase { repeatedPattern: BlueNoisePattern densityMap: ProcLayer diff --git a/src/datacontainers/WorldChunk.ts b/src/datacontainers/WorldChunk.ts new file mode 100644 index 0000000..75318d9 --- /dev/null +++ b/src/datacontainers/WorldChunk.ts @@ -0,0 +1,73 @@ +import { Box3, MathUtils, Vector3 } from "three" +import { ChunkKey } from "../common/types" +import { ChunkFactory } from "../index" +import { BlockData, BlockMode } from "./BlocksPatch" + +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 blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type + 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 + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 58d9976..0587b52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,14 +4,14 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' +export { GroundPatch } from './datacontainers/GroundPatch' export { BoardContainer } from './datacontainers/BoardContainer' export { EntityType } from './common/types' export { PatchesMap } from './datacontainers/PatchesMap' export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' -export { GroundPatch } from './datacontainers/GroundPatch' export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' export { ChunkFactory } from './tools/ChunkFactory' -export { WorldComputeApi } from './api/WorldComputeApi' +export { WorldComputeProxy } from './api/WorldComputeProxy' export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 271d691..61c07f2 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,18 +1,9 @@ -import { Box3, MathUtils, Vector2, Vector3 } from 'three' - -import { EntityData, PatchBlock, PatchId } from '../common/types' -import { asVect2, asVect3 } from '../common/utils' -import { BlockData, BlockMode } from '../datacontainers/BlocksPatch' -import { BlockType, WorldConf } from '../index' - -import { TreeGenerators } from './TreeGenerator' - -// for debug use only -const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return WorldConf.debug.patchBordersHighlightColor && (localPos.x === 1 || localPos.z === 1) - ? WorldConf.debug.patchBordersHighlightColor - : blockType -} +import { PatchId } from '../common/types' +import { asVect3, chunkBoxFromId, serializeChunkId } from '../common/utils' +import { BlockMode } from '../datacontainers/BlocksPatch' +import { EntityChunk } from '../datacontainers/EntityChunk' +import { WorldChunk, WorldChunkStub } from '../datacontainers/WorldChunk' +import { BlockType, GroundPatch, WorldConf } from '../index' export class ChunkFactory { // eslint-disable-next-line no-use-before-define @@ -36,106 +27,6 @@ export class ChunkFactory { this.chunksRange.ymax = ymax } - writeChunkBlocks( - chunkDataContainer: Uint16Array, - chunkBbox: Box3, - blockLocalPos: Vector3, - blockData: BlockData, - bufferOver: Uint16Array | [], - ) { - const chunk_size = chunkBbox.getSize(new Vector3()).x // Math.round(Math.pow(chunkDataContainer.length, 1 / 3)) - - let written_blocks_count = 0 - - const level = MathUtils.clamp( - blockLocalPos.y + bufferOver.length, - chunkBbox.min.y, - chunkBbox.max.y, - ) - let buff_index = Math.max(level - blockLocalPos.y, 0) - let h = level - chunkBbox.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 && - chunkDataContainer[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 - chunkDataContainer[blocksIndex] = this.voxelDataEncoder( - blockType, - blockMode, - ) - blockType && written_blocks_count++ - } - h-- - buff_index-- - depth++ - } - return written_blocks_count - } - - voxelizeGround( - blockIterator: Generator, - chunkDataContainer: Uint16Array, - chunkBox: Box3, - ) { - let written_blocks_count = 0 - for (const block of blockIterator) { - 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 - written_blocks_count += this.writeChunkBlocks( - chunkDataContainer, - chunkBox, - blockLocalPos, - blockData, - block.buffer || [], - ) - } - return written_blocks_count - } - - mergeEntityChunkData( - entityDataIterator: Generator, - chunkData: Uint16Array, - chunkBox: Box3, - ) { - let writtenBlocksCount = 0 - // iter over entity blocks - for (const entityBlock of entityDataIterator) { - const entityLocalPos = entityBlock.localPos as Vector3 - if (entityBlock.buffer) { - entityLocalPos.x += 1 - entityLocalPos.z += 1 - // bmin.y = block.localPos.y - writtenBlocksCount += this.writeChunkBlocks( - chunkData, - chunkBox, - entityLocalPos, - entityBlock.data, - entityBlock.buffer, - ) - } - } - return writtenBlocksCount - } - genChunksIdsFromPatchId(patchId: PatchId) { const { ymin, ymax } = this.chunksRange const chunk_ids = [] @@ -145,14 +36,26 @@ export class ChunkFactory { } return chunk_ids } - /** - * Assembles world building blocks (GroundPatch, ChunkEntity) together + * chunkify or chunksAssembly + * Assembles world building blocks (GroundPatch, EntityChunk) together * to form final world chunk */ - chunksAssemby(groundPatches:Ground) { - // chunkify ground patches - // retrieve and chunkify entities - // merge entities chunks with ground chunk + chunkify(patch: GroundPatch, patchEntities: EntityChunk[]) { + const patchChunkIds = patch.id ? ChunkFactory.default.genChunksIdsFromPatchId(patch.id) : [] + const worldChunksStubs = patchChunkIds.map(chunkId => { + const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) + const worldChunk = new WorldChunk(chunkBox) + // Ground pass + patch.fillChunk(worldChunk) + // Entities pass + patchEntities.forEach(entityChunk => patch.mergeEntityVoxels(entityChunk, worldChunk)) + const worldChunkStub: WorldChunkStub = { + key: serializeChunkId(chunkId), + data: worldChunk.chunkData + } + return worldChunkStub + }) + return worldChunksStubs } } From d7f7bf327bf32ba40233e07b85cd0097f49fd6cc Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 4 Sep 2024 08:28:19 +0000 Subject: [PATCH 36/45] feat: functional board container with worker support + use low-level data copy operation --- src/datacontainers/BlocksPatch.ts | 81 ++++---- src/datacontainers/BoardContainer.ts | 291 ++++++++++++++++----------- src/datacontainers/DataContainers.ts | 105 +++++----- 3 files changed, 273 insertions(+), 204 deletions(-) diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 91a02d7..874b7c7 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -4,6 +4,7 @@ import { Block, PatchBlock, EntityData, + PatchKey, } from '../common/types' import { patchBoxFromKey, @@ -11,6 +12,7 @@ import { parseThreeStub, asVect3, asVect2, + serializePatchId, } from '../common/utils' import { BlockType } from '../procgen/Biome' import { WorldConf } from '../index' @@ -29,10 +31,9 @@ export type BlockData = { } export type PatchStub = { - key: string - bounds: Box3 - rawDataContainer: Uint32Array - entities: EntityData[] + key?: string + bounds: Box2 + rawData: Uint32Array } // bits allocated per block data type @@ -56,18 +57,33 @@ const parseBoundsOrKeyInput = (patchBoundsOrKey: Box2 | string) => { } export class BlocksPatch extends DataContainer { - rawDataContainer: Uint32Array + rawData!: Uint32Array margin = 0 + key = '' // needed for patch export + patchId: Vector2 | undefined - key: string | null - id: Vector2 | null - - constructor(patchBoundsOrKey: Box2 | string, margin = 1) { + constructor(patchBoundsOrKey: Box2 | PatchKey = new Box2(), margin = 1) { super(parseBoundsOrKeyInput(patchBoundsOrKey)) - this.key = typeof patchBoundsOrKey === "string" ? patchBoundsOrKey : null - this.id = this.key ? parsePatchKey(this.key) : null + const patchId = typeof patchBoundsOrKey === "string" ? parsePatchKey(patchBoundsOrKey) : null + if (patchId) { + this.id = patchId + } this.margin = margin - this.rawDataContainer = new Uint32Array(this.extendedDims.x * this.extendedDims.y) + this.init(this.bounds) + } + + override init(bounds: Box2): void { + super.init(bounds) + this.rawData = new Uint32Array(this.extendedDims.x * this.extendedDims.y) + } + + get id() { + return this.patchId + } + + set id(patchId: Vector2 | undefined) { + this.patchId = patchId + this.key = serializePatchId(patchId) } get extendedBox() { @@ -87,26 +103,6 @@ export class BlocksPatch extends DataContainer { return this.localBox.expandByScalar(this.margin) } - /** - * @param targetBox if unspecified will be whole source container - */ - copyContentOverTarget(targetBox: Box2) { - const source = this - const targetInput = targetBox || source.bounds - const target = new BlocksPatch(targetInput) - super.copyContentOverTargetContainer(target) - } - - static fromStub(patchOrStub: BlocksPatch) { - const bounds = parseThreeStub(patchOrStub.bounds) as Box2 - const patch = new BlocksPatch(patchOrStub.key || bounds) - patchOrStub.rawDataContainer.forEach( - (rawVal, i) => patch.rawDataContainer[i] = rawVal) - patch.bounds.min.y = patchOrStub.bounds.min.y - patch.bounds.max.y = patchOrStub.bounds.max.y - return patch - } - decodeBlockData(rawData: number): BlockData { const shift = BlockDataBitAllocation const level = @@ -131,13 +127,13 @@ export class BlocksPatch extends DataContainer { } readBlockData(blockIndex: number): BlockData { - const blockRawData = this.rawDataContainer[blockIndex] + const blockRawData = this.rawData[blockIndex] const blockData = this.decodeBlockData(blockRawData as number) return blockData } writeBlockData(blockIndex: number, blockData: BlockData) { - this.rawDataContainer[blockIndex] = this.encodeBlockData(blockData) + this.rawData[blockIndex] = this.encodeBlockData(blockData) } adjustRangeBox(rangeBox: Box2 | Vector2, local = false) { @@ -158,11 +154,11 @@ export class BlocksPatch extends DataContainer { asVect2(this.toLocalPos(asVect3(rangeMax)))) } - override getIndex(localPos: Vector3) { + override getIndex(localPos: Vector2 | Vector3) { + localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) return ( (localPos.x + this.margin) * this.extendedDims.x + - localPos.z + - this.margin + localPos.y + this.margin ) } @@ -242,10 +238,19 @@ export class BlocksPatch extends DataContainer { } } + fromStub(patchStub: PatchStub) { + this.init(parseThreeStub(patchStub.bounds) as Box2) + this.id = patchStub.key ? parsePatchKey(patchStub.key) : this.id + this.rawData.set(patchStub.rawData) + this.bounds.min.y = patchStub.bounds.min.y + this.bounds.max.y = patchStub.bounds.max.y + return this + } + // getBlocksRow(zRowIndex: number) { // const rowStart = zRowIndex * this.dimensions.y // const rowEnd = rowStart + this.dimensions.x - // const rowRawData = this.rawDataContainer.slice(rowStart, rowEnd) + // const rowRawData = this.rawData.slice(rowStart, rowEnd) // return rowRawData // } diff --git a/src/datacontainers/BoardContainer.ts b/src/datacontainers/BoardContainer.ts index 0ae8c5c..ef1476f 100644 --- a/src/datacontainers/BoardContainer.ts +++ b/src/datacontainers/BoardContainer.ts @@ -1,21 +1,25 @@ import { Box2, Vector2, Vector3 } from 'three' import { Block, PatchBlock } from '../common/types' -import { asVect2, asVect3 } from '../common/utils' -import { WorldCacheContainer, WorldConf } from '../index' +import { asBox2, asBox3, asVect2, asVect3 } from '../common/utils' +import { BlockType, GroundPatch, WorldCacheContainer, WorldCompute, WorldConf } from '../index' import { PseudoDistributionMap } from './RandomDistributionMap' -import { BlockMode, BlocksPatch } from './BlocksPatch' +import { BlockMode, BlocksPatch, PatchStub } from './BlocksPatch' import { PatchesMap } from './PatchesMap' -export type BoardData = { - box: Box2, - blocks: [], +export type BoardParams = { + radius: number, + maxThickness: number +} + +export type BoardRawStub = PatchStub & { entities: { startPos: Block[], holes: Block[], obstacles: Block[], - } + }, + params: BoardParams & { center: Vector3 } } const getDefaultPatchDim = () => @@ -40,9 +44,10 @@ const holesDistParams = { tries: 20, } -// const initBoardBox = () +const computeBoardBox = (center: Vector3, radius: number) => + new Box2().setFromCenterAndSize(asVect2(center), new Vector2(radius, radius).multiplyScalar(2)) -export class BoardContainer extends BlocksPatch { +export class BoardContainer extends GroundPatch { // static singleton: BoardContainer // static get instance(){ // return this.singleton @@ -51,31 +56,47 @@ export class BoardContainer extends BlocksPatch { static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) // board instance params - boardCenter - boardRadius - boardMaxHeightDiff + center + radius + thickness + // board overriden block + boardBox: Box2 + swapBuffer!: Uint32Array + + constructor(boardCenter = new Vector3(), boardRadius = 0, boardThickness = 0) { + super(computeBoardBox(boardCenter, boardRadius), 0) + this.radius = boardRadius + this.center = boardCenter.clone().floor() + this.boardBox = new Box2(asVect2(this.center), asVect2(this.center)) + this.thickness = boardThickness + // this.initBoard() + } + + // initBoard(boardCenter:Vector3, boardRadius:number, boardThickness:number){ - constructor(center: Vector3, radius: number, maxHeightDiff: number) { - super(new Box2()) - this.boardRadius = radius - this.boardCenter = center.clone().floor() - this.boardMaxHeightDiff = maxHeightDiff - const board_dims = new Vector2(radius, radius).multiplyScalar(2) - this.bbox.setFromCenterAndSize(asVect2(this.boardCenter), board_dims) - this.init(this.bbox) + // } + + /** + * switch between original and overriden buffer + */ + swapBuffers() { + const bufferSave = this.rawData + this.rawData = this.swapBuffer + this.swapBuffer = bufferSave } - initBoard(): void { + buildBoard(): void { this.shapeBoard() - console.log(this.dataExport()) - this.showStartPositions() - this.digHoles() - this.trimTrees() + // this.debug() + // console.log(this.dataExport()) + // this.showStartPositions() + // this.digHoles() + // this.trimTrees() } restoreOriginalPatches() { const original_patches_container = new PatchesMap(getDefaultPatchDim()) - original_patches_container.init(this.bbox) + original_patches_container.init(this.bounds) original_patches_container.populateFromExisting( WorldCacheContainer.instance.availablePatches, true, @@ -85,34 +106,75 @@ export class BoardContainer extends BlocksPatch { isWithinBoard(blockPos: Vector3) { let isInsideBoard = false + const { thickness, radius } = this if (blockPos) { - const heightDiff = Math.abs(blockPos.y - this.boardCenter.y) - const dist = asVect2(blockPos).distanceTo(asVect2(this.boardCenter)) - isInsideBoard = - dist <= this.boardRadius && heightDiff <= this.boardMaxHeightDiff + const heightDiff = Math.abs(blockPos.y - this.center.y) + const dist = asVect2(blockPos).distanceTo(asVect2(this.center)) + isInsideBoard = dist <= radius && heightDiff <= thickness } return isInsideBoard } + // overrideBlock(block: PatchBlock) { + // const blockData = block.data + // blockData.level = this.center.y + // blockData.mode = BlockMode.BOARD_CONTAINER + // return block + // } + overrideBlock(block: PatchBlock) { const blockData = block.data - blockData.level = this.boardCenter.y - blockData.mode = BlockMode.BOARD_CONTAINER + // blockData.level = this.center.y + if (this.isWithinBoard(block.pos)) { + blockData.mode = BlockMode.BOARD_CONTAINER + blockData.level = this.center.y + blockData.type = BlockType.DBG_ORANGE + } return block } + *iterBoardBlock() { + const blocks = this.iterBlocksQuery(undefined, true) + // const blocks = this.iterPatchesBlocks() + for (const block of blocks) { + // discard blocks not included in board shape + // if (this.isWithinBoard(block.pos)) { + yield block + // } + } + } + + shapeBoard() { + // const { ymin, ymax } = this.getMinMax() + // const avg = Math.round(ymin + (ymax - ymin) / 2) + // reset bbox to refine bounds + const tempContainer = new BlocksPatch(this.bounds, 0) + tempContainer.rawData.fill(0) + // const boardBlocks = this.iterBlocksQuery(undefined, true); + const boardBlocks = this.iterBoardBlock() + for (const block of boardBlocks) { + const boardBlock = this.overrideBlock(block) + tempContainer.writeBlockData(boardBlock.index, boardBlock.data) + // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) + // if (this.isWithinBoard(block.pos)) { + this.boardBox.expandByPoint(asVect2(block.pos)) + // } + } + // copy content over + this.swapBuffer = tempContainer.copySubContent(this.boardBox) + } + getBoardEntities(distMap: PseudoDistributionMap, entityRadius = 2) { const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= entityRadius const spawnLocs = distMap.querySpawnLocations( - this.bbox, + this.bounds, intersectsEntity, () => 1, ) const entities = spawnLocs .map(loc => { const startPos = asVect3(loc) - const patch = this.findPatch(startPos) - const block = patch?.getBlock(startPos, false) + const block = this.getBlock(startPos, false) return block }) .filter( @@ -122,36 +184,6 @@ export class BoardContainer extends BlocksPatch { return entities } - *iterBoardBlock() { - for (const patch of this.availablePatches) { - const blocks = patch.iterBlocksQuery(undefined, false) - // const blocks = this.iterPatchesBlocks() - for (const block of blocks) { - // discard blocks not included in board shape - if (this.isWithinBoard(block.pos)) { - yield block - } - } - } - } - - shapeBoard() { - // const { ymin, ymax } = this.getMinMax() - // const avg = Math.round(ymin + (ymax - ymin) / 2) - // reset bbox to refine bounds - const blocksContainer = new BlocksPatch(this.bbox, 0) - const adjustedBox = new Box2(asVect2(this.boardCenter), asVect2(this.boardCenter)) - - const boardBlocks = this.iterBoardBlock() - for (const block of boardBlocks) { - const boardBlock = this.overrideBlock(block) - blocksContainer.setBlock(boardBlock.pos, boardBlock.data, false) - adjustedBox.expandByPoint(asVect2(boardBlock.pos)) - // yield boardBlock - } - const finalBlocksContainer = new BlocksPatch(adjustedBox) - } - smoothEdges() { } showStartPositions() { @@ -185,73 +217,78 @@ export class BoardContainer extends BlocksPatch { } trimTrees() { - this.availablePatches.forEach(patch => { - patch.entities.forEach(entity => { - const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = this.findPatch(entityCenter)?.getBlock( - entityCenter, - false, - ) - entityCenter.y = entity.bbox.min.y - const isEntityOverlappingBoard = () => { - const entityBlocks = patch.iterBlocksQuery(entity.bbox) - for (const block of entityBlocks) { - if (this.isWithinBoard(block.pos)) { - return true - } + const treeEntities = WorldCompute.queryEntities(this.bounds) + treeEntities.forEach(entity => { + const entityCenter = entity.bbox.getCenter(new Vector3()) + const entityCenterBlock = this.getBlock( + entityCenter, + false, + ) + entityCenter.y = entity.bbox.min.y + const isEntityOverlappingBoard = () => { + const entityBlocks = this.iterBlocksQuery(asBox2(entity.bbox)) + for (const block of entityBlocks) { + if (this.isWithinBoard(block.pos)) { + return true } - return false } + return false + } - if (entityCenterBlock && this.isWithinBoard(entityCenterBlock.pos)) { - // trim entities belonging to board - const diff = entityCenter.clone().sub(this.boardCenter) - // const radius = 3 - // const entityCenterPos = entityCenterBlock.pos - // entity.bbox.min.x = entityCenterPos.x - radius - // entity.bbox.max.x = entityCenterPos.x + radius - // entity.bbox.min.z = entityCenterPos.z - radius - // entity.bbox.max.z = entityCenterPos.z + radius - entity.bbox.max.y = entity.bbox.min.y + 2 - Math.min(diff.y, 0) - // const entityBlocks = patch.iterEntityBlocks(entity) - // // check if a block is outside the board and belongs to another patch - // for (const block of entityBlocks) { - // if (!this.isInsideBoard(block) && !this.findPatch(block.pos)?.bbox.equals(patch.bbox)) { - // // discard entity - // entity.bbox.makeEmpty() - // } - // } - } else if (isEntityOverlappingBoard()) { - // discard outside entities having an overlap with the board - entity.bbox.makeEmpty() - } - // else render outside entities with no overlap as usual - }) + if (entityCenterBlock && this.isWithinBoard(entityCenterBlock.pos)) { + // trim entities belonging to board + const diff = entityCenter.clone().sub(this.center) + // const radius = 3 + // const entityCenterPos = entityCenterBlock.pos + // entity.bbox.min.x = entityCenterPos.x - radius + // entity.bbox.max.x = entityCenterPos.x + radius + // entity.bbox.min.z = entityCenterPos.z - radius + // entity.bbox.max.z = entityCenterPos.z + radius + entity.bbox.max.y = entity.bbox.min.y + 2 - Math.min(diff.y, 0) + // const entityBlocks = patch.iterEntityBlocks(entity) + // // check if a block is outside the board and belongs to another patch + // for (const block of entityBlocks) { + // if (!this.isInsideBoard(block) && !this.findPatch(block.pos)?.bbox.equals(patch.bbox)) { + // // discard entity + // entity.bbox.makeEmpty() + // } + // } + } else if (isEntityOverlappingBoard()) { + // discard outside entities having an overlap with the board + entity.bbox.makeEmpty() + } + // else render outside entities with no overlap as usual }) } - copyBoardData() { + dataExport() { } - dataExport() { + rawDataExport() { const { startPosDistribution, holesDistribution } = BoardContainer - const startPos = this.getBoardEntities(startPosDistribution) - .map(block => ({ - pos: block.pos, - data: block.data - })) - const holes = this.getBoardEntities(holesDistribution) - .map(block => ({ - pos: block.pos, - data: block.data - })) - // TODO refactor: consider trees as no longer attached to ground patch - // but retrievable like any other entities + const { center, radius, thickness } = this + const startPos: Block[] = [] + // this.getBoardEntities(startPosDistribution) + // .map(block => ({ + // pos: block.pos, + // data: block.data + // })) + const holes: Block[] = [] + // this.getBoardEntities(holesDistribution) + // .map(block => ({ + // pos: block.pos, + // data: block.data + // })) const obstacles: Block[] = [] - const boardData: BoardData = { - box: this.bbox, - blocks: [], + const boardData: BoardRawStub = { + bounds: this.boardBox, + params: { + center, + radius, + maxThickness: thickness + }, + rawData: this.swapBuffer, entities: { startPos, holes, @@ -260,4 +297,16 @@ export class BoardContainer extends BlocksPatch { } return boardData } + + override fromStub(boardStub: BoardRawStub) { + super.fromStub(boardStub) + if (boardStub.params) { + const { center, radius, maxThickness } = boardStub.params + this.center = center + this.radius = radius + this.thickness = maxThickness + } + this.boardBox = boardStub.bounds + return this + } } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 2db036a..34aa0d1 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -1,63 +1,76 @@ -import { Vector2, Box2, Vector3 } from 'three' +import { Vector2, Box2, Vector3, Box3 } from 'three' import { asVect2, asVect3, getPatchId, patchUpperId } from '../common/utils' -// export enum BitLength { -// Uint16, -// Uint32 -// } - -// const getArrayConstructor = (bitLength: BitLength) => { -// switch (bitLength) { -// case BitLength.Uint16: -// return Uint16Array -// case BitLength.Uint32: -// return Uint32Array -// } -// } - - /** * Multi purpose data container */ export abstract class DataContainer { - bounds: Box2 - dimensions: Vector2 - abstract rawDataContainer: T + bounds!: Box2 + dimensions!: Vector2 + abstract rawData: T - constructor(bounds: Box2) {//, bitLength = BitLength.Uint16) { + constructor(bounds = new Box2()) {//, bitLength = BitLength.Uint16) { + this.init(bounds) + // this.rawData = getArrayConstructor(bitLength) + } + + init(bounds: Box2) { this.bounds = bounds this.dimensions = bounds.getSize(new Vector2()) - // this.rawDataContainer = getArrayConstructor(bitLength) } /** * @param target target container to copy data to */ - copyContentOverTargetContainer(target: DataContainer) { - const source = this - // const targetInput = targetBox || source.bounds - // const target = new DataContainer(targetInput) - const localMin = target.bounds.min.clone().sub(source.bounds.min) - const localMax = target.bounds.max.clone().sub(source.bounds.max) - const rowLength = localMax.x - localMin.x - const rowStartPos = localMin.clone() - const sourceContainer = source.rawDataContainer - const targetContainer = target.rawDataContainer - let targetIndex = 0 - while (rowStartPos.y < localMax.y) - for (let yRowIndex = localMin.y; yRowIndex < localMax.y; yRowIndex++) { - // const startIndex = this.getBlockIndex(new Vector2(zRowIndex, )) - let remaining = rowLength - let sourceIndex = this.getIndex(asVect3(rowStartPos)) - while (remaining--) { - const rawVal = sourceContainer[sourceIndex] || 0 - targetContainer[targetIndex] = rawVal - sourceIndex++ - targetIndex++ + copySubContent(subBounds: Box2): T { + const targetDims = subBounds.getSize(new Vector2()); + const targetSize = targetDims.x * targetDims.y; + const { bounds } = this; + const source = this.rawData; + + // Create a new typed array of the same type as the source + const target = new (this.rawData.constructor as { new(length: number): T })(targetSize); + + // Calculate local offsets relative to the main bounds + const localMin = subBounds.min.clone().sub(bounds.min); + const localMax = subBounds.max.clone().sub(bounds.min); // Corrected subtraction + + const rowLength = localMax.x - localMin.x; + + // Efficiently copy each row + for (let yIndex = localMin.y; yIndex < localMax.y; yIndex++) { + // inverted index to stick to current order TODO use new system + const sourceStartIndex = this.getIndex(new Vector2(yIndex, localMin.x)); + const targetStartIndex = (yIndex - localMin.y) * rowLength; + target.set(source.subarray(sourceStartIndex, sourceStartIndex + rowLength), targetStartIndex); + } + + return target; // Return the copied subcontent + } + + overrideContent(source: DataContainer) { + const target = this; + if (source.bounds.intersectsBox(target.bounds)) { + const overlap = target.bounds.clone().intersect(source.bounds); + console.log(`overlapping => override content`) + for (let x = overlap.min.x; x < overlap.max.x; x++) { + // const globalStartPos = new Vector3(x, 0, overlap.min.y) + const globalStartPos = new Vector3(x, 0, overlap.min.y) + const targetLocalStartPos = target.toLocalPos(globalStartPos) + const sourceLocalStartPos = source.toLocalPos(globalStartPos) + let targetIndex = target.getIndex(targetLocalStartPos) + let sourceIndex = source.getIndex(sourceLocalStartPos) + for (let y = overlap.min.y; y < overlap.max.y; y++) { + const sourceVal = source.rawData[sourceIndex++] + if (sourceVal) { + target.rawData[targetIndex++] = sourceVal + } } - rowStartPos.y++ } + } } + + inLocalRange(localPos: Vector3 | Vector2) { localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) return ( @@ -78,8 +91,9 @@ export abstract class DataContainer { ) } - getIndex(localPos: Vector3) { - return localPos.x * this.dimensions.x + localPos.z + getIndex(localPos: Vector2 | Vector3) { + localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) + return localPos.y * this.dimensions.x + localPos.x; } // toLocalPos(pos: T): T @@ -103,6 +117,7 @@ export abstract class DataContainer { // blockPos.z < this.bounds.max.z // ) } + // abstract get chunkIds(): ChunkId[] // abstract toChunks(): any } From ce464ca0708f9b302784198a79220d41dc0230bf Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 4 Sep 2024 15:41:43 +0000 Subject: [PATCH 37/45] fix: support margin blocks in low level data copy --- src/api/world-compute.ts | 9 ++++----- src/datacontainers/BlocksPatch.ts | 14 ++++++++++++-- src/datacontainers/DataContainers.ts | 12 +++++++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 19c052f..5a5f9ee 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -91,11 +91,10 @@ export const bakeGroundPatch = (patchKeyOrBox: PatchKey | Box2) => { // Battle board export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams) => { - const { radius, maxThickness } = boardParams - const boardMap = new BoardContainer(boardPos, radius, maxThickness) - boardMap.fill() - boardMap.buildBoard() - const boardData = boardMap.rawDataExport() + const boardMap = new BoardContainer(boardPos, boardParams) + boardMap.fill() // fill with ground data + boardMap.buildBoard() // override with board data + const boardData = boardMap.exportRawData() return boardData } diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index 874b7c7..fb39dae 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -86,12 +86,12 @@ export class BlocksPatch extends DataContainer { this.key = serializePatchId(patchId) } - get extendedBox() { + get extendedBounds() { return this.bounds.clone().expandByScalar(this.margin) } get extendedDims() { - return this.extendedBox.getSize(new Vector2()) + return this.extendedBounds.getSize(new Vector2()) } get localBox() { @@ -238,6 +238,16 @@ export class BlocksPatch extends DataContainer { } } + toStub() { + const { bounds, rawData } = this + const patchStub: PatchStub = { + bounds, + rawData + } + if (this.key && this.key !== '') patchStub.key = this.key + return patchStub + } + fromStub(patchStub: PatchStub) { this.init(parseThreeStub(patchStub.bounds) as Box2) this.id = patchStub.key ? parsePatchKey(patchStub.key) : this.id diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 34aa0d1..a189dde 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -48,10 +48,20 @@ export abstract class DataContainer { return target; // Return the copied subcontent } - overrideContent(source: DataContainer) { + overrideContent(source: DataContainer, margin = 0) { const target = this; + + 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) console.log(`overlapping => override content`) for (let x = overlap.min.x; x < overlap.max.x; x++) { // const globalStartPos = new Vector3(x, 0, overlap.min.y) From 330a75b7c8d2e669cc2e978eb14475fef9f46ac6 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 4 Sep 2024 19:15:05 +0000 Subject: [PATCH 38/45] fix: restore all board functionalities --- src/api/WorldComputeProxy.ts | 4 +- src/api/world-compute.ts | 1 - src/datacontainers/BoardContainer.ts | 275 ++++++++++++--------------- src/datacontainers/DataContainers.ts | 1 - 4 files changed, 123 insertions(+), 158 deletions(-) diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index 847d214..9b3b81c 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,9 +1,9 @@ import { Box2, Vector3 } from 'three' -import { Block, EntityKey, PatchKey } from '../common/types' +import { Block, PatchKey } from '../common/types' import { BoardContainer, BoardParams } from '../datacontainers/BoardContainer' import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' -import { BlocksPatch, GroundPatch, WorldCompute, WorldUtils } from '../index' +import { GroundPatch, WorldCompute, WorldUtils } from '../index' export enum ComputeApiCall { PatchCompute = 'bakeGroundPatch', diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 5a5f9ee..f5811b8 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -93,7 +93,6 @@ export const bakeGroundPatch = (patchKeyOrBox: PatchKey | Box2) => { export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams) => { const boardMap = new BoardContainer(boardPos, boardParams) boardMap.fill() // fill with ground data - boardMap.buildBoard() // override with board data const boardData = boardMap.exportRawData() return boardData } diff --git a/src/datacontainers/BoardContainer.ts b/src/datacontainers/BoardContainer.ts index ef1476f..98da275 100644 --- a/src/datacontainers/BoardContainer.ts +++ b/src/datacontainers/BoardContainer.ts @@ -1,32 +1,35 @@ import { Box2, Vector2, Vector3 } from 'three' -import { Block, PatchBlock } from '../common/types' -import { asBox2, asBox3, asVect2, asVect3 } from '../common/utils' -import { BlockType, GroundPatch, WorldCacheContainer, WorldCompute, WorldConf } from '../index' +import { Block, EntityData, PatchBlock } from '../common/types' +import { asBox2, asVect2, asVect3 } from '../common/utils' +import { BlockType, GroundPatch, WorldCompute, WorldConf } from '../index' import { PseudoDistributionMap } from './RandomDistributionMap' import { BlockMode, BlocksPatch, PatchStub } from './BlocksPatch' -import { PatchesMap } from './PatchesMap' export type BoardParams = { radius: number, - maxThickness: number + maxThickness: number, + keepLast?: boolean } export type BoardRawStub = PatchStub & { entities: { - startPos: Block[], + startPositions: Block[], holes: Block[], obstacles: Block[], }, params: BoardParams & { center: Vector3 } } -const getDefaultPatchDim = () => - new Vector2(WorldConf.patchSize, WorldConf.patchSize) +const defaultBoardParams: BoardParams = { + radius: 0, + maxThickness: 0, + keepLast: false +} /** - * Entities distribution default conf + * Board entities distribution conf */ // Start positions const startPosDistParams = { @@ -44,10 +47,9 @@ const holesDistParams = { tries: 20, } -const computeBoardBox = (center: Vector3, radius: number) => - new Box2().setFromCenterAndSize(asVect2(center), new Vector2(radius, radius).multiplyScalar(2)) - export class BoardContainer extends GroundPatch { + // used for handling previous board removal + static prevContainerBounds: Box2 | undefined // static singleton: BoardContainer // static get instance(){ // return this.singleton @@ -59,49 +61,29 @@ export class BoardContainer extends GroundPatch { center radius thickness - // board overriden block - boardBox: Box2 - swapBuffer!: Uint32Array - - constructor(boardCenter = new Vector3(), boardRadius = 0, boardThickness = 0) { - super(computeBoardBox(boardCenter, boardRadius), 0) - this.radius = boardRadius - this.center = boardCenter.clone().floor() - this.boardBox = new Box2(asVect2(this.center), asVect2(this.center)) - this.thickness = boardThickness - // this.initBoard() - } - - // initBoard(boardCenter:Vector3, boardRadius:number, boardThickness:number){ - - // } + swapContainer!: BlocksPatch //Uint32Array - /** - * switch between original and overriden buffer - */ - swapBuffers() { - const bufferSave = this.rawData - this.rawData = this.swapBuffer - this.swapBuffer = bufferSave + static getInitialBounds = (center: Vector3, radius: number, includePrevBounds = false) => { + const initialBounds = new Box2() + .setFromCenterAndSize(asVect2(center), new Vector2(radius, radius) + .multiplyScalar(2)) + if (includePrevBounds && BoardContainer.prevContainerBounds) { + initialBounds.union(BoardContainer.prevContainerBounds) + } + return initialBounds } - buildBoard(): void { - this.shapeBoard() - // this.debug() - // console.log(this.dataExport()) - // this.showStartPositions() - // this.digHoles() - // this.trimTrees() + constructor(boardCenter = new Vector3(), boardParams = defaultBoardParams) { + super(BoardContainer.getInitialBounds(boardCenter, boardParams.radius, boardParams.keepLast)) + const { radius, maxThickness } = boardParams + this.radius = radius + this.center = boardCenter.clone().floor() + this.thickness = maxThickness } - restoreOriginalPatches() { - const original_patches_container = new PatchesMap(getDefaultPatchDim()) - original_patches_container.init(this.bounds) - original_patches_container.populateFromExisting( - WorldCacheContainer.instance.availablePatches, - true, - ) - return original_patches_container + getOriginalBounds() { + const { radius, center } = this + return BoardContainer.getInitialBounds(center, radius) } isWithinBoard(blockPos: Vector3) { @@ -114,21 +96,15 @@ export class BoardContainer extends GroundPatch { } return isInsideBoard } - - // overrideBlock(block: PatchBlock) { - // const blockData = block.data - // blockData.level = this.center.y - // blockData.mode = BlockMode.BOARD_CONTAINER - // return block - // } - + overrideBlock(block: PatchBlock) { const blockData = block.data - // blockData.level = this.center.y if (this.isWithinBoard(block.pos)) { blockData.mode = BlockMode.BOARD_CONTAINER blockData.level = this.center.y - blockData.type = BlockType.DBG_ORANGE + // blockData.type = BlockType.DBG_ORANGE + } else { + // blockData.type = BlockType.DBG_ORANGE } return block } @@ -147,150 +123,119 @@ export class BoardContainer extends GroundPatch { shapeBoard() { // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) - // reset bbox to refine bounds - const tempContainer = new BlocksPatch(this.bounds, 0) - tempContainer.rawData.fill(0) - // const boardBlocks = this.iterBlocksQuery(undefined, true); - const boardBlocks = this.iterBoardBlock() + const tempContainer = new BlocksPatch(this.bounds) + const originalBounds = this.getOriginalBounds() + const finalBounds = new Box2(asVect2(this.center), asVect2(this.center)) + const boardBlocks = this.iterBlocksQuery(undefined, false); + // const boardBlocks = this.iterBoardBlock() for (const block of boardBlocks) { - const boardBlock = this.overrideBlock(block) - tempContainer.writeBlockData(boardBlock.index, boardBlock.data) - // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) + const boardBlock = this.overrideBlock(block) + tempContainer.writeBlockData(boardBlock.index, boardBlock.data) + // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) // if (this.isWithinBoard(block.pos)) { - this.boardBox.expandByPoint(asVect2(block.pos)) + finalBounds.expandByPoint(asVect2(block.pos)) // } } - // copy content over - this.swapBuffer = tempContainer.copySubContent(this.boardBox) + // copy content over final container + const finalBoardContainer = new BlocksPatch(finalBounds) + finalBoardContainer.rawData = tempContainer.copySubContent(finalBounds.clone().expandByScalar(1)) + // finalContainer.rawData = this.copySubContent(this.extendedBounds) + return finalBoardContainer } - getBoardEntities(distMap: PseudoDistributionMap, entityRadius = 2) { + smoothEdges() { } + + getBoardEntities(boardContainer: BlocksPatch, distMap: PseudoDistributionMap, entityRadius = 2) { const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= entityRadius const spawnLocs = distMap.querySpawnLocations( - this.bounds, + boardContainer.bounds, intersectsEntity, () => 1, ) const entities = spawnLocs .map(loc => { const startPos = asVect3(loc) - const block = this.getBlock(startPos, false) + const block = boardContainer.getBlock(startPos, false) return block }) .filter( - ent => ent && this.isWithinBoard(ent.pos), + block => block && this.isWithinBoard(block.pos), ) as PatchBlock[] // TODO prune entities spawning over existing entities return entities } - smoothEdges() { } - - showStartPositions() { - const startPositions = this.getBoardEntities(BoardContainer.startPosDistribution) + genStartPositions(boardContainer: BlocksPatch) { + const startPositions = this.getBoardEntities(boardContainer, BoardContainer.startPosDistribution) WorldConf.debug.boardStartPosHighlightColor && startPositions.forEach(block => { - const patch = this.findPatch(block.pos) - if (patch && block) { - block.data.type = WorldConf.debug.boardStartPosHighlightColor - block.data.mode = BlockMode.DEFAULT - patch.writeBlockData(block.index, block.data) - // patch.setBlock(block.pos, block.data) - } + block.data.type = WorldConf.debug.boardStartPosHighlightColor + block.data.mode = BlockMode.DEFAULT + // this.swapContainer.writeBlockData(block.index, block.data) + boardContainer.setBlock(block.pos, block.data) }) return startPositions } - digHoles() { - const holes = this.getBoardEntities(BoardContainer.holesDistribution) + digHoles(boardContainer: BlocksPatch) { + const holes = this.getBoardEntities(boardContainer, BoardContainer.holesDistribution) WorldConf.debug.boardHolesHighlightColor && holes.forEach(block => { - const patch = this.findPatch(block.pos) - if (patch && block) { - block.data.type = WorldConf.debug.boardHolesHighlightColor - block.data.level -= 1 // dig hole in the ground - block.data.mode = BlockMode.DEFAULT - patch.writeBlockData(block.index, block.data) - // patch.setBlock(block.pos, block.data) - } + block.data.type = WorldConf.debug.boardHolesHighlightColor + block.data.level -= 1 // dig hole in the ground + block.data.mode = BlockMode.DEFAULT + // this.swapContainer.writeBlockData(block.index, block.data) + boardContainer.setBlock(block.pos, block.data) }) + return holes } - trimTrees() { - const treeEntities = WorldCompute.queryEntities(this.bounds) - treeEntities.forEach(entity => { + trimTrees(boardContainer: BlocksPatch) { + const treeEntities = WorldCompute.queryEntities(boardContainer.bounds) + const trunks = treeEntities.map(entity => { const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = this.getBlock( + const entityCenterBlock = boardContainer.getBlock( entityCenter, false, ) entityCenter.y = entity.bbox.min.y - const isEntityOverlappingBoard = () => { - const entityBlocks = this.iterBlocksQuery(asBox2(entity.bbox)) - for (const block of entityBlocks) { - if (this.isWithinBoard(block.pos)) { - return true - } - } - return false - } - - if (entityCenterBlock && this.isWithinBoard(entityCenterBlock.pos)) { - // trim entities belonging to board - const diff = entityCenter.clone().sub(this.center) - // const radius = 3 - // const entityCenterPos = entityCenterBlock.pos - // entity.bbox.min.x = entityCenterPos.x - radius - // entity.bbox.max.x = entityCenterPos.x + radius - // entity.bbox.min.z = entityCenterPos.z - radius - // entity.bbox.max.z = entityCenterPos.z + radius - entity.bbox.max.y = entity.bbox.min.y + 2 - Math.min(diff.y, 0) - // const entityBlocks = patch.iterEntityBlocks(entity) - // // check if a block is outside the board and belongs to another patch - // for (const block of entityBlocks) { - // if (!this.isInsideBoard(block) && !this.findPatch(block.pos)?.bbox.equals(patch.bbox)) { - // // discard entity - // entity.bbox.makeEmpty() - // } - // } - } else if (isEntityOverlappingBoard()) { - // discard outside entities having an overlap with the board - entity.bbox.makeEmpty() - } - // else render outside entities with no overlap as usual + return entityCenterBlock }) - } - - dataExport() { + .filter(trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos)) as PatchBlock[] + trunks.forEach(trunkBlock => { + trunkBlock.data.type = BlockType.TREE_TRUNK + trunkBlock.data.mode = BlockMode.DEFAULT + trunkBlock.data.level += 1 + boardContainer.setBlock(trunkBlock.pos, trunkBlock.data) + }) + return trunks } - rawDataExport() { - const { startPosDistribution, holesDistribution } = BoardContainer + exportRawData() { const { center, radius, thickness } = this - const startPos: Block[] = [] - // this.getBoardEntities(startPosDistribution) - // .map(block => ({ - // pos: block.pos, - // data: block.data - // })) - const holes: Block[] = [] - // this.getBoardEntities(holesDistribution) - // .map(block => ({ - // pos: block.pos, - // data: block.data - // })) - const obstacles: Block[] = [] + const boardContainer = this.shapeBoard() + const startPositions: Block[] = this.genStartPositions(boardContainer) + .map(block => ({ + pos: block.pos, + data: block.data + })) + const holes: Block[] = this.digHoles(boardContainer) + .map(block => ({ + pos: block.pos, + data: block.data + })) + const obstacles: Block[] = this.trimTrees(boardContainer) + const containerStub = boardContainer.toStub() const boardData: BoardRawStub = { - bounds: this.boardBox, + ...containerStub, params: { center, radius, maxThickness: thickness }, - rawData: this.swapBuffer, entities: { - startPos, + startPositions, holes, obstacles, } @@ -298,6 +243,18 @@ export class BoardContainer extends GroundPatch { return boardData } + exportData() { + const rawStub = this.exportRawData() + const { entities, bounds } = rawStub + const data: BlockType[] = [] + const boardStub = { + bounds, + data, + entities + } + return boardStub + } + override fromStub(boardStub: BoardRawStub) { super.fromStub(boardStub) if (boardStub.params) { @@ -309,4 +266,14 @@ export class BoardContainer extends GroundPatch { this.boardBox = boardStub.bounds return this } + + isEntityOverlappingBoard = (entity: EntityData) => { + const entityBlocks = this.iterBlocksQuery(asBox2(entity.bbox)) + for (const block of entityBlocks) { + if (this.isWithinBoard(block.pos)) { + return true + } + } + return false + } } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index a189dde..88c07b0 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -62,7 +62,6 @@ export abstract class DataContainer { if (source.bounds.intersectsBox(target.bounds)) { const overlap = target.bounds.clone().intersect(source.bounds); adjustOverlapMargins(overlap) - console.log(`overlapping => override content`) for (let x = overlap.min.x; x < overlap.max.x; x++) { // const globalStartPos = new Vector3(x, 0, overlap.min.y) const globalStartPos = new Vector3(x, 0, overlap.min.y) From 4a5d51f807a987bbdf9e9693684b8d885096c5cf Mon Sep 17 00:00:00 2001 From: etienne Date: Thu, 5 Sep 2024 20:59:00 +0000 Subject: [PATCH 39/45] fix: init and data indexing troublesome issues --- src/api/world-compute.ts | 4 +-- src/common/utils.ts | 10 +++--- src/datacontainers/BlocksPatch.ts | 7 ++--- src/datacontainers/DataContainers.ts | 47 ++++++---------------------- src/index.ts | 2 ++ 5 files changed, 23 insertions(+), 47 deletions(-) diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index f5811b8..718edee 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,4 +1,4 @@ -import { Box2, Vector2, Vector3 } from 'three' +import { Box2, Vector3 } from 'three' import { BoardContainer, EntityType, GroundPatch } from '../index' import { Biome, BlockType } from '../procgen/Biome' @@ -101,7 +101,7 @@ export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams) => * Entity queries */ -export const queryEntities = (region: Box2 | Vector2) => { +export const queryEntities = (region: Box2) => { const spawnablePlaces = WorldEntities.instance.queryDistributionMap(EntityType.TREE_APPLE)(region) const spawnedEntities = spawnablePlaces .map(entLoc => WorldEntities.instance.getEntityData(EntityType.TREE_PINE, asVect3(entLoc))) diff --git a/src/common/utils.ts b/src/common/utils.ts index c5b5ffe..cda764c 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -221,7 +221,7 @@ const isVect2Stub = (stub: Vector2Like) => { stub !== undefined && stub.x !== undefined && stub.y !== undefined && - stub.z === undefined + (stub as any).z === undefined ) } @@ -308,9 +308,11 @@ const serializePatchId = (patchId: PatchId | undefined) => { const patchBoxFromKey = (patchKey: string, patchDims: Vector2) => { const patchCoords = parsePatchKey(patchKey) - const bmin = patchCoords.clone().multiply(patchDims) - const bmax = patchCoords.clone().addScalar(1).multiply(patchDims) - const bbox = new Box2(bmin, bmax) + const bbox = new Box2() + if (patchCoords) { + bbox.min = patchCoords.clone().multiply(patchDims) + bbox.max = patchCoords.clone().addScalar(1).multiply(patchDims) + } return bbox } diff --git a/src/datacontainers/BlocksPatch.ts b/src/datacontainers/BlocksPatch.ts index fb39dae..30d9602 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/BlocksPatch.ts @@ -1,9 +1,8 @@ -import { Box2, Box3, Vector2, Vector3 } from 'three' +import { Box2, Vector2, Vector3 } from 'three' import { Block, PatchBlock, - EntityData, PatchKey, } from '../common/types' import { @@ -69,7 +68,7 @@ export class BlocksPatch extends DataContainer { this.id = patchId } this.margin = margin - this.init(this.bounds) + this.rawData = new Uint32Array(this.extendedDims.x * this.extendedDims.y) } override init(bounds: Box2): void { @@ -157,7 +156,7 @@ export class BlocksPatch extends DataContainer { override getIndex(localPos: Vector2 | Vector3) { localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) return ( - (localPos.x + this.margin) * this.extendedDims.x + + (localPos.x + this.margin) * this.extendedDims.y + localPos.y + this.margin ) } diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 88c07b0..b3cfca1 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -1,4 +1,4 @@ -import { Vector2, Box2, Vector3, Box3 } from 'three' +import { Vector2, Box2, Vector3 } from 'three' import { asVect2, asVect3, getPatchId, patchUpperId } from '../common/utils' /** @@ -10,7 +10,8 @@ export abstract class DataContainer { abstract rawData: T constructor(bounds = new Box2()) {//, bitLength = BitLength.Uint16) { - this.init(bounds) + this.bounds = bounds + this.dimensions = bounds.getSize(new Vector2()) // this.rawData = getArrayConstructor(bitLength) } @@ -19,38 +20,8 @@ export abstract class DataContainer { this.dimensions = bounds.getSize(new Vector2()) } - /** - * @param target target container to copy data to - */ - copySubContent(subBounds: Box2): T { - const targetDims = subBounds.getSize(new Vector2()); - const targetSize = targetDims.x * targetDims.y; - const { bounds } = this; - const source = this.rawData; - - // Create a new typed array of the same type as the source - const target = new (this.rawData.constructor as { new(length: number): T })(targetSize); - - // Calculate local offsets relative to the main bounds - const localMin = subBounds.min.clone().sub(bounds.min); - const localMax = subBounds.max.clone().sub(bounds.min); // Corrected subtraction - - const rowLength = localMax.x - localMin.x; - - // Efficiently copy each row - for (let yIndex = localMin.y; yIndex < localMax.y; yIndex++) { - // inverted index to stick to current order TODO use new system - const sourceStartIndex = this.getIndex(new Vector2(yIndex, localMin.x)); - const targetStartIndex = (yIndex - localMin.y) * rowLength; - target.set(source.subarray(sourceStartIndex, sourceStartIndex + rowLength), targetStartIndex); - } - - return target; // Return the copied subcontent - } - - overrideContent(source: DataContainer, margin = 0) { - const target = this; - + // copy occurs only on the overlapping global pos region of both containers + static copySourceOverTargetContainer(source: DataContainer, target: DataContainer) { 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 @@ -70,10 +41,12 @@ export abstract class DataContainer { let targetIndex = target.getIndex(targetLocalStartPos) let sourceIndex = source.getIndex(sourceLocalStartPos) for (let y = overlap.min.y; y < overlap.max.y; y++) { - const sourceVal = source.rawData[sourceIndex++] + const sourceVal = source.rawData[sourceIndex] if (sourceVal) { - target.rawData[targetIndex++] = sourceVal + target.rawData[targetIndex] = sourceVal } + sourceIndex++ + targetIndex++ } } } @@ -102,7 +75,7 @@ export abstract class DataContainer { getIndex(localPos: Vector2 | Vector3) { localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) - return localPos.y * this.dimensions.x + localPos.x; + return localPos.x * this.dimensions.y + localPos.y; } // toLocalPos(pos: T): T diff --git a/src/index.ts b/src/index.ts index 0587b52..2ccc580 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,8 @@ export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' export { ChunkFactory } from './tools/ChunkFactory' export { WorldComputeProxy } from './api/WorldComputeProxy' +export { DataContainer } from './datacontainers/DataContainers' + export * as WorldCompute from './api/world-compute' export * as WorldUtils from './common/utils' From 1b2c5dc7a5f6037866e257f78c0cd51ea35fcac1 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 6 Sep 2024 14:45:24 +0000 Subject: [PATCH 40/45] feat: varying board hole areas + export format refactor --- src/api/WorldComputeProxy.ts | 8 +- src/api/world-compute.ts | 8 +- src/common/math.ts | 15 + src/datacontainers/BoardContainer.ts | 279 -------------- src/feats/BoardContainer.ts | 371 +++++++++++++++++++ src/{datacontainers => feats}/GroundPatch.ts | 7 +- src/index.ts | 4 +- src/misc/WorldConfig.ts | 2 +- src/procgen/Biome.ts | 1 + 9 files changed, 402 insertions(+), 293 deletions(-) create mode 100644 src/common/math.ts delete mode 100644 src/datacontainers/BoardContainer.ts create mode 100644 src/feats/BoardContainer.ts rename src/{datacontainers => feats}/GroundPatch.ts (92%) diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index 9b3b81c..009ccfb 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,7 +1,7 @@ import { Box2, Vector3 } from 'three' import { Block, PatchKey } from '../common/types' -import { BoardContainer, BoardParams } from '../datacontainers/BoardContainer' +import { BoardContainer, BoardParams } from '../feats/BoardContainer' import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' import { GroundPatch, WorldCompute, WorldUtils } from '../index' @@ -120,12 +120,12 @@ export class WorldComputeProxy { return entityChunks } - async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams) { + async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) { const boardData = !this.worker ? - WorldCompute.computeBoardData(boardCenter, boardParams) : + WorldCompute.computeBoardData(boardCenter, boardParams, lastBoardBounds) : await this.workerCall( ComputeApiCall.BattleBoardCompute, - [boardCenter, boardParams], + [boardCenter, boardParams, lastBoardBounds], ) const board = new BoardContainer().fromStub(boardData) return board diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index 718edee..c74aae2 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -8,7 +8,7 @@ import { Block, EntityData, PatchKey } from '../common/types' import { asBox3, asVect2, asVect3 } from '../common/utils' import { WorldEntities } from '../procgen/WorldEntities' import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' -import { BoardParams } from '../datacontainers/BoardContainer' +import { BoardParams } from '../feats/BoardContainer' /** * Individual blocks requests @@ -90,10 +90,10 @@ export const bakeGroundPatch = (patchKeyOrBox: PatchKey | Box2) => { } // Battle board -export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams) => { - const boardMap = new BoardContainer(boardPos, boardParams) +export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) => { + const boardMap = new BoardContainer(boardPos, boardParams, lastBoardBounds) boardMap.fill() // fill with ground data - const boardData = boardMap.exportRawData() + const boardData = boardMap.toStub() return boardData } diff --git a/src/common/math.ts b/src/common/math.ts new file mode 100644 index 0000000..74dfa47 --- /dev/null +++ b/src/common/math.ts @@ -0,0 +1,15 @@ +import { Box2, Vector2 } from "three"; + +export const findBoundingBox = (point: Vector2, points: Vector2[], bounds: Box2) => { + const { min, max } = bounds.clone() + + for (const p of points) { + min.x = p.x < point.x ? Math.max(p.x, min.x) : min.x + min.y = p.y < point.y ? Math.max(p.y, min.y) : min.y + max.x = p.x > point.x ? Math.min(p.x, max.x) : max.x + max.y = p.y > point.y ? Math.min(p.y, max.y) : max.y + } + + const bbox = new Box2(min, max) + return bbox +} \ No newline at end of file diff --git a/src/datacontainers/BoardContainer.ts b/src/datacontainers/BoardContainer.ts deleted file mode 100644 index 98da275..0000000 --- a/src/datacontainers/BoardContainer.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { Box2, Vector2, Vector3 } from 'three' - -import { Block, EntityData, PatchBlock } from '../common/types' -import { asBox2, asVect2, asVect3 } from '../common/utils' -import { BlockType, GroundPatch, WorldCompute, WorldConf } from '../index' - -import { PseudoDistributionMap } from './RandomDistributionMap' -import { BlockMode, BlocksPatch, PatchStub } from './BlocksPatch' - -export type BoardParams = { - radius: number, - maxThickness: number, - keepLast?: boolean -} - -export type BoardRawStub = PatchStub & { - entities: { - startPositions: Block[], - holes: Block[], - obstacles: Block[], - }, - params: BoardParams & { center: Vector3 } -} - -const defaultBoardParams: BoardParams = { - radius: 0, - maxThickness: 0, - keepLast: false -} - -/** - * Board entities distribution conf - */ -// Start positions -const startPosDistParams = { - aleaSeed: 'boardStartPos', - minDistance: 10, - maxDistance: 16, - tries: 20, -} - -// Holes -const holesDistParams = { - aleaSeed: 'boardHoles', - minDistance: 10, - maxDistance: 16, - tries: 20, -} - -export class BoardContainer extends GroundPatch { - // used for handling previous board removal - static prevContainerBounds: Box2 | undefined - // static singleton: BoardContainer - // static get instance(){ - // return this.singleton - // } - static holesDistribution = new PseudoDistributionMap(undefined, holesDistParams) - static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) - - // board instance params - center - radius - thickness - swapContainer!: BlocksPatch //Uint32Array - - static getInitialBounds = (center: Vector3, radius: number, includePrevBounds = false) => { - const initialBounds = new Box2() - .setFromCenterAndSize(asVect2(center), new Vector2(radius, radius) - .multiplyScalar(2)) - if (includePrevBounds && BoardContainer.prevContainerBounds) { - initialBounds.union(BoardContainer.prevContainerBounds) - } - return initialBounds - } - - constructor(boardCenter = new Vector3(), boardParams = defaultBoardParams) { - super(BoardContainer.getInitialBounds(boardCenter, boardParams.radius, boardParams.keepLast)) - const { radius, maxThickness } = boardParams - this.radius = radius - this.center = boardCenter.clone().floor() - this.thickness = maxThickness - } - - getOriginalBounds() { - const { radius, center } = this - return BoardContainer.getInitialBounds(center, radius) - } - - isWithinBoard(blockPos: Vector3) { - let isInsideBoard = false - const { thickness, radius } = this - if (blockPos) { - const heightDiff = Math.abs(blockPos.y - this.center.y) - const dist = asVect2(blockPos).distanceTo(asVect2(this.center)) - isInsideBoard = dist <= radius && heightDiff <= thickness - } - return isInsideBoard - } - - overrideBlock(block: PatchBlock) { - const blockData = block.data - if (this.isWithinBoard(block.pos)) { - blockData.mode = BlockMode.BOARD_CONTAINER - blockData.level = this.center.y - // blockData.type = BlockType.DBG_ORANGE - } else { - // blockData.type = BlockType.DBG_ORANGE - } - return block - } - - *iterBoardBlock() { - const blocks = this.iterBlocksQuery(undefined, true) - // const blocks = this.iterPatchesBlocks() - for (const block of blocks) { - // discard blocks not included in board shape - // if (this.isWithinBoard(block.pos)) { - yield block - // } - } - } - - shapeBoard() { - // const { ymin, ymax } = this.getMinMax() - // const avg = Math.round(ymin + (ymax - ymin) / 2) - const tempContainer = new BlocksPatch(this.bounds) - const originalBounds = this.getOriginalBounds() - const finalBounds = new Box2(asVect2(this.center), asVect2(this.center)) - const boardBlocks = this.iterBlocksQuery(undefined, false); - // const boardBlocks = this.iterBoardBlock() - for (const block of boardBlocks) { - const boardBlock = this.overrideBlock(block) - tempContainer.writeBlockData(boardBlock.index, boardBlock.data) - // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) - // if (this.isWithinBoard(block.pos)) { - finalBounds.expandByPoint(asVect2(block.pos)) - // } - } - // copy content over final container - const finalBoardContainer = new BlocksPatch(finalBounds) - finalBoardContainer.rawData = tempContainer.copySubContent(finalBounds.clone().expandByScalar(1)) - // finalContainer.rawData = this.copySubContent(this.extendedBounds) - return finalBoardContainer - } - - smoothEdges() { } - - getBoardEntities(boardContainer: BlocksPatch, distMap: PseudoDistributionMap, entityRadius = 2) { - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= entityRadius - const spawnLocs = distMap.querySpawnLocations( - boardContainer.bounds, - intersectsEntity, - () => 1, - ) - const entities = spawnLocs - .map(loc => { - const startPos = asVect3(loc) - const block = boardContainer.getBlock(startPos, false) - return block - }) - .filter( - block => block && this.isWithinBoard(block.pos), - ) as PatchBlock[] - // TODO prune entities spawning over existing entities - return entities - } - - genStartPositions(boardContainer: BlocksPatch) { - const startPositions = this.getBoardEntities(boardContainer, BoardContainer.startPosDistribution) - WorldConf.debug.boardStartPosHighlightColor && - startPositions.forEach(block => { - block.data.type = WorldConf.debug.boardStartPosHighlightColor - block.data.mode = BlockMode.DEFAULT - // this.swapContainer.writeBlockData(block.index, block.data) - boardContainer.setBlock(block.pos, block.data) - }) - return startPositions - } - - digHoles(boardContainer: BlocksPatch) { - const holes = this.getBoardEntities(boardContainer, BoardContainer.holesDistribution) - WorldConf.debug.boardHolesHighlightColor && - holes.forEach(block => { - block.data.type = WorldConf.debug.boardHolesHighlightColor - block.data.level -= 1 // dig hole in the ground - block.data.mode = BlockMode.DEFAULT - // this.swapContainer.writeBlockData(block.index, block.data) - boardContainer.setBlock(block.pos, block.data) - }) - return holes - } - - trimTrees(boardContainer: BlocksPatch) { - const treeEntities = WorldCompute.queryEntities(boardContainer.bounds) - const trunks = treeEntities.map(entity => { - const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = boardContainer.getBlock( - entityCenter, - false, - ) - entityCenter.y = entity.bbox.min.y - return entityCenterBlock - }) - .filter(trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos)) as PatchBlock[] - - trunks.forEach(trunkBlock => { - trunkBlock.data.type = BlockType.TREE_TRUNK - trunkBlock.data.mode = BlockMode.DEFAULT - trunkBlock.data.level += 1 - boardContainer.setBlock(trunkBlock.pos, trunkBlock.data) - }) - return trunks - } - - exportRawData() { - const { center, radius, thickness } = this - const boardContainer = this.shapeBoard() - const startPositions: Block[] = this.genStartPositions(boardContainer) - .map(block => ({ - pos: block.pos, - data: block.data - })) - const holes: Block[] = this.digHoles(boardContainer) - .map(block => ({ - pos: block.pos, - data: block.data - })) - const obstacles: Block[] = this.trimTrees(boardContainer) - const containerStub = boardContainer.toStub() - const boardData: BoardRawStub = { - ...containerStub, - params: { - center, - radius, - maxThickness: thickness - }, - entities: { - startPositions, - holes, - obstacles, - } - } - return boardData - } - - exportData() { - const rawStub = this.exportRawData() - const { entities, bounds } = rawStub - const data: BlockType[] = [] - const boardStub = { - bounds, - data, - entities - } - return boardStub - } - - override fromStub(boardStub: BoardRawStub) { - super.fromStub(boardStub) - if (boardStub.params) { - const { center, radius, maxThickness } = boardStub.params - this.center = center - this.radius = radius - this.thickness = maxThickness - } - this.boardBox = boardStub.bounds - return this - } - - isEntityOverlappingBoard = (entity: EntityData) => { - const entityBlocks = this.iterBlocksQuery(asBox2(entity.bbox)) - for (const block of entityBlocks) { - if (this.isWithinBoard(block.pos)) { - return true - } - } - return false - } -} diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts new file mode 100644 index 0000000..8fdb043 --- /dev/null +++ b/src/feats/BoardContainer.ts @@ -0,0 +1,371 @@ +import { Box2, Vector2, Vector3 } from 'three' + +import { Block, PatchBlock } from '../common/types' +import { asVect2, asVect3 } from '../common/utils' +import { BlockType, DataContainer, GroundPatch, ProcLayer, WorldCompute, WorldConf } from '../index' + +import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' +import { BlockMode, BlocksPatch, PatchStub } from '../datacontainers/BlocksPatch' +import { findBoundingBox } from '../common/math' + +export enum BoardBlockType { + FLAT = 0, + HOLE = 1, + OBSTACLE = 2, +} + +export type BoardBlock = { + blockType: BlockType, + subtype: BoardBlockType, +} + +export type BoardInputParams = { + radius: number, + thickness: number, +} + +export type BoardInput = BoardInputParams & { center: Vector3 } + +export type BoardOutputData = { + bounds: Box2, + data: BoardBlock[] +} + + +// map block type to board block type +const boardBlockTypeMapper = (blockType: BlockType) => { + switch (blockType) { + case BlockType.TREE_TRUNK: + return BoardBlockType.OBSTACLE + case BlockType.BOARD_HOLE: + return BoardBlockType.HOLE + default: + return BoardBlockType.FLAT + } +} + +export type BoardStub = PatchStub & +{ + input: BoardInput + output: BoardOutputData +} + +/** + * Board entities distribution conf + */ +// Start positions +// const startPosDistParams = { +// aleaSeed: 'boardStartPos', +// minDistance: 10, +// maxDistance: 16, +// tries: 20, +// } + +// Holes +const holesDistParams = { + aleaSeed: 'boardHoles', + minDistance: 10, + maxDistance: 16, + tries: 20, +} +/** + * Building steps + * - compute initial bounds from input + * - fill with ground blocks + * - override original blocks and adjust external bounds + * - add board entities (trimmed trees, holes) + * + * Board data export format: + * - bounds + * - data as array of block's type + */ +export class BoardContainer extends GroundPatch { + + // static prevContainerBounds: Box2 | undefined + // static singleton: BoardContainer + // static get instance(){ + // return this.singleton + // } + static holesDistribution = new PseudoDistributionMap(undefined, holesDistParams) + static holesMapDistribution = new ProcLayer("holesMap") + // static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) + + // board input params + input: BoardInput = { + center: new Vector3(), + radius: 0, + thickness: 0 + } + + // board output data + output: BoardOutputData = { + bounds: new Box2(), + data: [] + } + // swapContainer!: BlocksPatch //Uint32Array + + /** + * + * @param center + * @param radius + * @param previousBounds // used for handling previous board removal + * @returns + */ + static getInitialBounds = (center: Vector3, radius: number, previousBounds?: Box2) => { + // const previousBounds = BoardContainer.prevContainerBounds + const defaultBounds = new Box2() + .setFromCenterAndSize(asVect2(center), new Vector2(radius, radius) + .multiplyScalar(2)) + return previousBounds ? defaultBounds.union(previousBounds) : defaultBounds + } + + constructor(boardCenter = new Vector3(), boardParams?: BoardInputParams, lastBoardBounds?: Box2) { + super(BoardContainer.getInitialBounds(boardCenter, boardParams?.radius || 0, lastBoardBounds)) + const { input } = this + input.center = boardCenter.clone().floor() + input.radius = boardParams?.radius || input.radius + input.thickness = boardParams?.thickness || input.thickness + + BoardContainer.holesMapDistribution.sampling.periodicity = 0.25 + } + + isWithinBoard(blockPos: Vector3) { + let isInsideBoard = false + const { thickness, radius, center } = this.input + if (blockPos) { + const heightDiff = Math.abs(blockPos.y - center.y) + const dist = asVect2(blockPos).distanceTo(asVect2(center)) + isInsideBoard = dist <= radius && heightDiff <= thickness + } + return isInsideBoard + } + + isOverlappingWithBoard = (bounds: Box2) => { + const testedBlocks = this.iterBlocksQuery(bounds) + for (const block of testedBlocks) { + if (this.isWithinBoard(block.pos)) { + return true + } + } + return false + } + + overrideBlockData(block: PatchBlock) { + const blockData = block.data + + blockData.mode = BlockMode.BOARD_CONTAINER + blockData.level = this.input.center.y + blockData.type = this.isWithinBoard(block.pos) ? blockData.type : BlockType.DBG_ORANGE + + return blockData + } + + *iterBoardBlock() { + const blocks = this.iterBlocksQuery(undefined, true) + // const blocks = this.iterPatchesBlocks() + for (const block of blocks) { + // discard blocks not included in board shape + if (this.isWithinBoard(block.pos)) { + yield block + } + } + } + + /** + * Override original ground blocks with board blocks + * and adjust final board bounds + * @returns + */ + shapeBoard() { + const { center } = this.input + // const { ymin, ymax } = this.getMinMax() + // const avg = Math.round(ymin + (ymax - ymin) / 2) + const tempContainer = new BlocksPatch(this.bounds) + const finalBounds = new Box2(asVect2(center), asVect2(center)) + const boardBlocks = this.iterBlocksQuery(undefined, false); + // const boardBlocks = this.iterBoardBlock() + for (const block of boardBlocks) { + + // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) + if (this.isWithinBoard(block.pos)) { + tempContainer.writeBlockData(block.index, this.overrideBlockData(block)) + finalBounds.expandByPoint(asVect2(block.pos)) + } + } + // copy content over final container + const bounds = new Box2(asVect2(center), asVect2(center)) + bounds.expandByVector(new Vector2(1, 1).multiplyScalar(10)) + const finalBoardContainer = new GroundPatch(finalBounds) + DataContainer.copySourceOverTargetContainer(tempContainer, finalBoardContainer) + return finalBoardContainer + } + + smoothEdges() { } + + getBoardEntities(boardContainer: BlocksPatch, distMap: PseudoDistributionMap, entityRadius = 2) { + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= entityRadius + const spawnLocs = distMap.querySpawnLocations( + boardContainer.bounds, + intersectsEntity, + () => 1, + ) + const entities = spawnLocs + .map(loc => { + const startPos = asVect3(loc) + const block = boardContainer.getBlock(startPos, false) + return block + }) + .filter( + block => block && this.isWithinBoard(block.pos), + ) as PatchBlock[] + // TODO prune entities spawning over existing entities + return entities + } + + // Moved to SDK + // genStartPositions(boardContainer: BlocksPatch, otherEntities: PatchBlock[]) { + // const startPositions = this.getBoardEntities(boardContainer, BoardContainer.startPosDistribution) + // WorldConf.debug.boardStartPosHighlightColor && + // startPositions.forEach(block => { + // block.data.type = WorldConf.debug.boardStartPosHighlightColor + // block.data.mode = BlockMode.DEFAULT + // // this.swapContainer.writeBlockData(block.index, block.data) + // boardContainer.setBlock(block.pos, block.data) + // }) + // return startPositions + // } + + boardSplit(boardContainer: BlocksPatch) { + const { center } = this.input + const boardBlocks = boardContainer.iterBlocksQuery(undefined, false); + const dims = boardContainer.bounds.getSize(new Vector2()) + const check = (pos: Vector3) => dims.x < dims.y ? pos.z < center.z : pos.x < center.x + for (const block of boardBlocks) { + if (this.isWithinBoard(block.pos)) { + block.data.type = check(block.pos) ? BlockType.DBG_ORANGE : BlockType.DBG_GREEN + boardContainer.writeBlockData(block.index, block.data) + } + } + } + + isGroundHole(testPos: Vector3) { + return BoardContainer.holesMapDistribution.eval(testPos) < 0.15 + } + + digGroundHole(holeBlock: Block, boardContainer: BlocksPatch) { + holeBlock.data.type = BlockType.BOARD_HOLE + holeBlock.data.level -= 1 // dig hole in the ground + holeBlock.data.mode = BlockMode.DEFAULT + boardContainer.setBlock(holeBlock.pos, holeBlock.data) + } + + getHolesMonoBlocks(boardContainer: BlocksPatch) { + const holesSingleBlocks = this.getBoardEntities(boardContainer, BoardContainer.holesDistribution) + .map(({ pos, data }) => ({ pos, data })) + return holesSingleBlocks + } + + getHolesAreas(boardContainer: BlocksPatch, forbiddenBlocks: Block[]) { + const forbiddenPos = forbiddenBlocks.map(({ pos }) => asVect2(pos)) + const holesMono = this.getBoardEntities(boardContainer, BoardContainer.holesDistribution) + const holesMulti: PatchBlock[] = [] + // for each monoblock hole, find maximum bounding box around + holesMono.forEach(hole => { + const pos = asVect2(hole.pos) + const holeBounds = findBoundingBox(pos, forbiddenPos, boardContainer.bounds); + const holeBlocks = boardContainer.iterBlocksQuery(holeBounds) + for (const block of holeBlocks) { + holesMulti.push(block) + } + }) + return holesMulti.map(({ pos, data }) => ({ pos, data }) as Block) + } + + getHolesAreasBis(boardContainer: BlocksPatch, forbiddenBlocks: Block[]) { + // prevent holes from spreading over forbidden blocks + const isForbiddenPos = (testPos: Vector3) => !!forbiddenBlocks.find(block => block.pos.equals(testPos)) + const blocks = boardContainer.iterBlocksQuery() + const holes: Block[] = [] + for (const block of blocks) { + const testPos = block.pos + if (this.isWithinBoard(testPos) && this.isGroundHole(testPos) && !isForbiddenPos(testPos)) { + holes.push(block) + } + } + return holes.map(({ pos, data }) => ({ pos, data }) as Block) + } + + trimTrees(boardContainer: BlocksPatch) { + const treeEntities = WorldCompute.queryEntities(boardContainer.bounds) + const trunks = treeEntities.map(entity => { + const entityCenter = entity.bbox.getCenter(new Vector3()) + const entityCenterBlock = boardContainer.getBlock( + entityCenter, + false, + ) + entityCenter.y = entity.bbox.min.y + return entityCenterBlock + }) + .filter(trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos)) as PatchBlock[] + + trunks.forEach(trunkBlock => { + trunkBlock.data.type = BlockType.TREE_TRUNK + trunkBlock.data.mode = BlockMode.DEFAULT + trunkBlock.data.level += 1 + boardContainer.setBlock(trunkBlock.pos, trunkBlock.data) + }) + return trunks.map(({ pos, data }) => ({ pos, data }) as Block) + } + + getOutputContainer() { + const outputContainer = this.shapeBoard() + WorldConf.debug.boardStartSideColoring && this.boardSplit(outputContainer) + // const boardEntitiesBlocks: Block[] = [] + const obstacles: Block[] = this.trimTrees(outputContainer) + const holes: Block[] = this.getHolesAreasBis(outputContainer, obstacles) + holes.forEach(block => this.digGroundHole(block, outputContainer)) + this.output.bounds = outputContainer.bounds + return outputContainer + } + + override fromStub(boardStub: BoardStub) { + super.fromStub(boardStub) + const { input, output } = boardStub + this.input = input + this.output = output + return this + } + + override toStub(): BoardStub { + const outputContainer = this.getOutputContainer() + DataContainer.copySourceOverTargetContainer(outputContainer, this) + const { input, output } = this + const boardStub: BoardStub = { + ...super.toStub(), + input, + output + } + return boardStub + } + + exportBoardData() { + const outputContainer = this.getOutputContainer() + const boardBlocks = outputContainer.iterBlocksQuery() + for (const block of boardBlocks) { + const blockType = block.data.type + const boardBlock: BoardBlock = { + blockType, + subtype: boardBlockTypeMapper(blockType) + } + this.output.data.push(boardBlock) + } + const { bounds, data } = this.output + const boardData: BoardOutputData = { + bounds, + data + } + // optional raw data export + // return includeRawData ? toStub() : boardData + return boardData + } +} diff --git a/src/datacontainers/GroundPatch.ts b/src/feats/GroundPatch.ts similarity index 92% rename from src/datacontainers/GroundPatch.ts rename to src/feats/GroundPatch.ts index 0864ac1..95d0d1e 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/feats/GroundPatch.ts @@ -1,9 +1,9 @@ import { Vector3 } from "three"; import { asBox2 } from "../common/utils"; import { BlockType, WorldCompute, WorldConf } from "../index"; -import { BlocksPatch } from "./BlocksPatch"; -import { EntityChunk } from "./EntityChunk"; -import { WorldChunk } from "./WorldChunk"; +import { BlocksPatch } from "../datacontainers/BlocksPatch"; +import { EntityChunk } from "../datacontainers/EntityChunk"; +import { WorldChunk } from "../datacontainers/WorldChunk"; // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { @@ -48,6 +48,7 @@ export class GroundPatch extends BlocksPatch { } } + // TODO rename mergeWithEntities mergeEntityVoxels(entityChunk: EntityChunk, worldChunk: WorldChunk) { // return overlapping blocks between entity and container const patchBlocksIter = this.iterBlocksQuery(asBox2(entityChunk.chunkBox)) diff --git a/src/index.ts b/src/index.ts index 2ccc580..7647c98 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,8 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' -export { GroundPatch } from './datacontainers/GroundPatch' -export { BoardContainer } from './datacontainers/BoardContainer' +export { GroundPatch } from './feats/GroundPatch' +export { BoardContainer } from './feats/BoardContainer' export { EntityType } from './common/types' export { PatchesMap } from './datacontainers/PatchesMap' export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index 157a142..e3a6dcc 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -14,6 +14,6 @@ export class WorldConf { static debug = { patchBordersHighlightColor: BlockType.NONE, boardStartPosHighlightColor: BlockType.NONE, - boardHolesHighlightColor: BlockType.NONE, + boardStartSideColoring: false } } diff --git a/src/procgen/Biome.ts b/src/procgen/Biome.ts index dce1f01..98f23ca 100644 --- a/src/procgen/Biome.ts +++ b/src/procgen/Biome.ts @@ -25,6 +25,7 @@ export enum BlockType { MUD, ROCK, SNOW, + BOARD_HOLE, DBG_LIGHT, DBG_DARK, DBG_PURPLE, From 4dce24d6da5a9d26830fd1095d8f1eecbc24feb0 Mon Sep 17 00:00:00 2001 From: etienne Date: Sat, 7 Sep 2024 08:14:00 +0000 Subject: [PATCH 41/45] feat: now using compute proxy for built-in patch filling + refactor patch containers --- src/api/WorldComputeProxy.ts | 110 +++--- src/api/world-compute.ts | 92 ++--- src/common/math.ts | 28 +- src/common/types.ts | 2 +- src/common/utils.ts | 11 +- src/datacontainers/DataContainers.ts | 317 ++++++++++-------- src/datacontainers/EntityChunk.ts | 164 +++++---- .../{BlocksPatch.ts => GroundPatch.ts} | 131 ++++---- src/datacontainers/GroundPatchesMap.ts | 17 +- src/datacontainers/PatchesMap.ts | 15 +- src/datacontainers/RandomDistributionMap.ts | 13 +- src/datacontainers/WorldChunk.ts | 122 +++---- src/feats/BoardContainer.ts | 206 ++++++++---- src/feats/GroundPatch.ts | 76 ----- src/index.ts | 3 +- src/misc/WorldConfig.ts | 6 +- src/procgen/BlueNoisePattern.ts | 132 ++++---- src/procgen/WorldEntities.ts | 96 +++--- src/tools/ChunkFactory.ts | 22 +- 19 files changed, 837 insertions(+), 726 deletions(-) rename src/datacontainers/{BlocksPatch.ts => GroundPatch.ts} (70%) delete mode 100644 src/feats/GroundPatch.ts diff --git a/src/api/WorldComputeProxy.ts b/src/api/WorldComputeProxy.ts index 009ccfb..12e524d 100644 --- a/src/api/WorldComputeProxy.ts +++ b/src/api/WorldComputeProxy.ts @@ -1,16 +1,17 @@ import { Box2, Vector3 } from 'three' -import { Block, PatchKey } from '../common/types' -import { BoardContainer, BoardParams } from '../feats/BoardContainer' +import { Block, EntityData, PatchKey } from '../common/types' import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' import { GroundPatch, WorldCompute, WorldUtils } from '../index' +import { parseThreeStub } from '../common/utils' export enum ComputeApiCall { PatchCompute = 'bakeGroundPatch', BlocksBatchCompute = 'computeBlocksBatch', OvergroundBufferCompute = 'computeOvergroundBuffer', + QueryEntities = 'queryEntities', BakeEntities = 'queryBakeEntities', - BattleBoardCompute = 'computeBoardData' + BattleBoardCompute = 'computeBoardData', } export type ComputeApiParams = Partial<{ @@ -24,7 +25,9 @@ export type ComputeApiParams = Partial<{ * When provided all request are proxied to worker instead of main thread */ export class WorldComputeProxy { + // eslint-disable-next-line no-use-before-define static singleton: WorldComputeProxy + // eslint-disable-next-line no-undef workerInstance: Worker | undefined resolvers: Record = {} count = 0 @@ -38,6 +41,7 @@ export class WorldComputeProxy { return this.workerInstance } + // eslint-disable-next-line no-undef set worker(workerInstance: Worker | undefined) { this.workerInstance = workerInstance if (workerInstance) { @@ -67,24 +71,25 @@ export class WorldComputeProxy { this.worker.postMessage({ id, apiName, args }) return new Promise(resolve => (this.resolvers[id] = resolve)) } - return + return null } async computeBlocksBatch( blockPosBatch: Vector3[], params = { includeEntitiesBlocks: false }, ) { - 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[] + 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[]) return blocks } @@ -96,38 +101,65 @@ 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 *iterPatchCompute(patchKeysBatch: PatchKey[]) { for (const patchKey of patchKeysBatch) { - const patch = !this.worker ? WorldCompute.bakeGroundPatch(patchKey) : - await this.workerCall( - ComputeApiCall.PatchCompute, - [patchKey], // [emptyPatch.bbox] - )?.then(patchStub => new GroundPatch().fromStub(patchStub)) as GroundPatch + const patch = !this.worker + ? WorldCompute.bakeGroundPatch(patchKey) + : ((await this.workerCall( + ComputeApiCall.PatchCompute, + [patchKey], // [emptyPatch.bbox] + )?.then(patchStub => + new GroundPatch().fromStub(patchStub), + )) as GroundPatch) yield patch } } - 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 bakeGroundPatch(boundsOrPatchKey: Box2 | string) { + const patchStub = !this.worker + ? WorldCompute.bakeGroundPatch(boundsOrPatchKey) + : await this.workerCall(ComputeApiCall.PatchCompute, [boundsOrPatchKey]) + // ?.then(patchStub => new GroundPatch().fromStub(patchStub)) as GroundPatch + + return patchStub } - async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) { - const boardData = !this.worker ? - WorldCompute.computeBoardData(boardCenter, boardParams, lastBoardBounds) : - await this.workerCall( - ComputeApiCall.BattleBoardCompute, - [boardCenter, boardParams, lastBoardBounds], - ) - const board = new BoardContainer().fromStub(boardData) - return board + 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) : + // await this.workerCall( + // ComputeApiCall.BattleBoardCompute, + // [boardCenter, boardParams, lastBoardBounds], + // ) + // const board = new BoardContainer().fromStub(boardData) + // return board + // } } diff --git a/src/api/world-compute.ts b/src/api/world-compute.ts index c74aae2..82259a3 100644 --- a/src/api/world-compute.ts +++ b/src/api/world-compute.ts @@ -1,24 +1,24 @@ import { Box2, Vector3 } from 'three' -import { BoardContainer, EntityType, GroundPatch } from '../index' +import { EntityType, GroundPatch } from '../index' import { Biome, BlockType } from '../procgen/Biome' import { Heightmap } from '../procgen/Heightmap' -import { BlockData } from '../datacontainers/BlocksPatch' import { Block, EntityData, PatchKey } from '../common/types' import { asBox3, asVect2, asVect3 } from '../common/utils' import { WorldEntities } from '../procgen/WorldEntities' import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk' -import { BoardParams } from '../feats/BoardContainer' +import { BlockData } from '../datacontainers/GroundPatch' +// import { BoardInputParams } from '../feats/BoardContainer' /** * Individual blocks requests */ /** - * - * @param blockPosBatch - * @param params - * @returns + * + * @param blockPosBatch + * @param params + * @returns */ export const computeBlocksBatch = ( blockPosBatch: Vector3[], @@ -31,14 +31,13 @@ export const computeBlocksBatch = ( 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) - })[0] + 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 lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt) if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) { @@ -83,39 +82,60 @@ export const computeGroundBlock = (blockPos: Vector3) => { */ // Ground -export const bakeGroundPatch = (patchKeyOrBox: PatchKey | Box2) => { - const groundPatch = new GroundPatch(patchKeyOrBox) - groundPatch.fill() +export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => { + const groundPatch = new GroundPatch(boundsOrPatchKey) + const { min, max } = groundPatch.bounds + const blocks = groundPatch.iterBlocksQuery(undefined, false) + const level = { + min: 512, + max: 0, + } + let blockIndex = 0 + for (const block of blocks) { + const blockData = computeGroundBlock(block.pos) + level.min = Math.min(min.y, blockData.level) + level.max = Math.max(max.y, blockData.level) + groundPatch.writeBlockData(blockIndex, blockData) + blockIndex++ + } return groundPatch } // Battle board -export const computeBoardData = (boardPos: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) => { - const boardMap = new BoardContainer(boardPos, boardParams, lastBoardBounds) - boardMap.fill() // fill with ground data - const boardData = boardMap.toStub() - return boardData -} +// export const computeBoardData = (boardPos: Vector3, boardParams: BoardInputParams, lastBoardBounds: Box2) => { +// const boardMap = new BoardContainer(boardPos, boardParams, lastBoardBounds) +// await boardMap.fillGroundData() +// await boardMap.populateEntities() +// const boardStub = boardMap.toStub() +// return boardStub +// } /** - * Entity queries + * Entity queries/baking */ -export const queryEntities = (region: Box2) => { - const spawnablePlaces = WorldEntities.instance.queryDistributionMap(EntityType.TREE_APPLE)(region) +export const queryEntities = (queriedRegion: Box2) => { + const spawnablePlaces = WorldEntities.instance.queryDistributionMap( + EntityType.TREE_APPLE, + )(queriedRegion) const spawnedEntities = spawnablePlaces - .map(entLoc => WorldEntities.instance.getEntityData(EntityType.TREE_PINE, asVect3(entLoc))) + .map(entLoc => + WorldEntities.instance.getEntityData( + EntityType.TREE_PINE, + asVect3(entLoc), + ), + ) .filter(entity => confirmFinalizeEntity(entity)) return spawnedEntities } /** - * - * @param entityPos - * @returns + * + * @param entityPos + * @returns */ const confirmFinalizeEntity = (entity: EntityData) => { - const entityPos = entity.bbox.getCenter(new Vector3) + 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) @@ -127,13 +147,9 @@ const confirmFinalizeEntity = (entity: EntityData) => { entity.bbox.max.y += entity.bbox.min.y return entity } - return + return null } -/** - * Entities baking - */ - export const queryBakeEntities = (queriedRange: Box2) => { const entitiesData = queryEntities(queriedRange) return bakeEntitiesBatch(entitiesData) @@ -161,4 +177,4 @@ export const bakeEntitiesBatch = (entities: EntityData[]) => { // */ // const confirmSpawnability = () => { -// } \ No newline at end of file +// } diff --git a/src/common/math.ts b/src/common/math.ts index 74dfa47..9d37d68 100644 --- a/src/common/math.ts +++ b/src/common/math.ts @@ -1,15 +1,19 @@ -import { Box2, Vector2 } from "three"; +import { Box2, Vector2 } from 'three' -export const findBoundingBox = (point: Vector2, points: Vector2[], bounds: Box2) => { - const { min, max } = bounds.clone() +export const findBoundingBox = ( + point: Vector2, + points: Vector2[], + bounds: Box2, +) => { + const { min, max } = bounds.clone() - for (const p of points) { - min.x = p.x < point.x ? Math.max(p.x, min.x) : min.x - min.y = p.y < point.y ? Math.max(p.y, min.y) : min.y - max.x = p.x > point.x ? Math.min(p.x, max.x) : max.x - max.y = p.y > point.y ? Math.min(p.y, max.y) : max.y - } + for (const p of points) { + min.x = p.x < point.x ? Math.max(p.x, min.x) : min.x + min.y = p.y < point.y ? Math.max(p.y, min.y) : min.y + max.x = p.x > point.x ? Math.min(p.x, max.x) : max.x + max.y = p.y > point.y ? Math.min(p.y, max.y) : max.y + } - const bbox = new Box2(min, max) - return bbox -} \ No newline at end of file + const bbox = new Box2(min, max) + return bbox +} diff --git a/src/common/types.ts b/src/common/types.ts index 6a74b0d..d21a182 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,6 +1,6 @@ import { Box3, Vector2, Vector3 } from 'three' -import { BlockData } from '../datacontainers/BlocksPatch' +import { BlockData } from '../datacontainers/GroundPatch' import { BiomeType, BlockType } from '../procgen/Biome' import { LinkedList } from './misc' diff --git a/src/common/utils.ts b/src/common/utils.ts index cda764c..c47aa90 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,4 +1,5 @@ import { Box2, Box3, Vector2, Vector2Like, Vector3, Vector3Like } from 'three' + import { WorldConf } from '../index' import { @@ -271,9 +272,13 @@ const parseBox3Stub = (stub: Box3) => { } const parseThreeStub = (stub: any) => { - return stub ? parseBox3Stub(stub) || parseVect3Stub(stub) - || parseBox2Stub(stub) || parseVect2Stub(stub) - || stub : stub + return stub + ? parseBox3Stub(stub) || + parseVect3Stub(stub) || + parseBox2Stub(stub) || + parseVect2Stub(stub) || + stub + : stub } const parsePatchKey = (patchKey: PatchKey) => { diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index b3cfca1..565e60d 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -1,149 +1,204 @@ import { Vector2, Box2, Vector3 } from 'three' -import { asVect2, asVect3, getPatchId, patchUpperId } from '../common/utils' + +import { PatchKey } from '../common/types' +import { + asVect2, + asVect3, + getPatchId, + parsePatchKey, + patchBoxFromKey, + patchUpperId, + serializePatchId, +} from '../common/utils' +import { WorldConf } from '../index' + +const getDefaultPatchDim = () => + new Vector2(WorldConf.patchSize, WorldConf.patchSize) /** - * Multi purpose data container + * Multi purpose low level data container */ export abstract class DataContainer { - bounds!: Box2 - dimensions!: Vector2 - abstract rawData: T - - constructor(bounds = new Box2()) {//, bitLength = BitLength.Uint16) { - this.bounds = bounds - this.dimensions = bounds.getSize(new Vector2()) - // this.rawData = getArrayConstructor(bitLength) + bounds: Box2 + dimensions: Vector2 + margin = 0 + key = '' // needed for patch export + patchId: Vector2 | undefined + abstract rawData: T + + constructor(boundsOrPatchKey: Box2 | PatchKey = new Box2(), margin = 0) { + //, bitLength = BitLength.Uint16) { + const bounds = + boundsOrPatchKey instanceof Box2 + ? boundsOrPatchKey.clone() + : patchBoxFromKey(boundsOrPatchKey, getDefaultPatchDim()) + this.bounds = bounds + this.dimensions = bounds.getSize(new Vector2()) + this.margin = margin + const patchId = + typeof boundsOrPatchKey === 'string' + ? parsePatchKey(boundsOrPatchKey) + : null + if (patchId) { + this.id = patchId } - - init(bounds: Box2) { - this.bounds = bounds - this.dimensions = bounds.getSize(new Vector2()) + // this.rawData = getArrayConstructor(bitLength) + } + + get id() { + return this.patchId + } + + set id(patchId: Vector2 | undefined) { + this.patchId = patchId + this.key = serializePatchId(patchId) + } + + get extendedBounds() { + return this.bounds.clone().expandByScalar(this.margin) + } + + get extendedDims() { + return this.extendedBounds.getSize(new Vector2()) + } + + get localBox() { + const localBox = new Box2(new Vector2(0), this.dimensions.clone()) + return localBox + } + + get localExtendedBox() { + return this.localBox.expandByScalar(this.margin) + } + + init(bounds: Box2) { + this.bounds = bounds + this.dimensions = bounds.getSize(new Vector2()) + } + + // 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 global pos region of both containers - static copySourceOverTargetContainer(source: DataContainer, target: DataContainer) { - 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 x = overlap.min.x; x < overlap.max.x; x++) { - // const globalStartPos = new Vector3(x, 0, overlap.min.y) - const globalStartPos = new Vector3(x, 0, overlap.min.y) - const targetLocalStartPos = target.toLocalPos(globalStartPos) - const sourceLocalStartPos = source.toLocalPos(globalStartPos) - let targetIndex = target.getIndex(targetLocalStartPos) - let sourceIndex = source.getIndex(sourceLocalStartPos) - for (let y = overlap.min.y; y < overlap.max.y; y++) { - const sourceVal = source.rawData[sourceIndex] - if (sourceVal) { - target.rawData[targetIndex] = sourceVal - } - sourceIndex++ - targetIndex++ - } - } + if (source.bounds.intersectsBox(target.bounds)) { + const overlap = target.bounds.clone().intersect(source.bounds) + adjustOverlapMargins(overlap) + for (let { x } = overlap.min; x < overlap.max.x; x++) { + // const globalStartPos = new Vector3(x, 0, overlap.min.y) + const globalStartPos = new Vector3(x, 0, overlap.min.y) + const targetLocalStartPos = target.toLocalPos(globalStartPos) + const sourceLocalStartPos = source.toLocalPos(globalStartPos) + let targetIndex = target.getIndex(targetLocalStartPos) + let sourceIndex = source.getIndex(sourceLocalStartPos) + for (let { y } = overlap.min; y < overlap.max.y; y++) { + const sourceVal = source.rawData[sourceIndex] + if (sourceVal) { + target.rawData[targetIndex] = sourceVal + } + sourceIndex++ + targetIndex++ } + } } - - - inLocalRange(localPos: Vector3 | Vector2) { - localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) - return ( - localPos.x >= 0 && - localPos.x < this.dimensions.x && - localPos.y >= 0 && - localPos.y < this.dimensions.y - ) - } - - inGlobalRange(globalPos: Vector3 | Vector2) { - globalPos = globalPos instanceof Vector2 ? globalPos : asVect2(globalPos) - 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 - ) - } - - getIndex(localPos: Vector2 | Vector3) { - localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) - return localPos.x * this.dimensions.y + localPos.y; - } - - // toLocalPos(pos: T): T - // toGlobalPos(pos: T): T - - toLocalPos(pos: Vector3) { - const origin = asVect3(this.bounds.min.clone()) - return pos.clone().sub(origin) - } - - toGlobalPos(pos: Vector3) { - const origin = asVect3(this.bounds.min.clone()) - return origin.add(pos) - } - containsPoint(pos: Vector3) { - return this.bounds.containsPoint(asVect2(pos)) - // return ( - // blockPos.x >= this.bounds.min.x && - // blockPos.z >= this.bounds.min.z && - // blockPos.x < this.bounds.max.x && - // blockPos.z < this.bounds.max.z - // ) - } - - // abstract get chunkIds(): ChunkId[] - // abstract toChunks(): any + } + + inLocalRange(localPos: Vector3 | Vector2) { + localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) + return ( + localPos.x >= 0 && + localPos.x < this.dimensions.x && + localPos.y >= 0 && + localPos.y < this.dimensions.y + ) + } + + inGlobalRange(globalPos: Vector3 | Vector2) { + globalPos = globalPos instanceof Vector2 ? globalPos : asVect2(globalPos) + 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 + ) + } + + getIndex(localPos: Vector2 | Vector3) { + localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) + return localPos.x * this.dimensions.y + localPos.y + } + + // toLocalPos(pos: T): T + // toGlobalPos(pos: T): T + + toLocalPos(pos: Vector3) { + const origin = asVect3(this.bounds.min.clone()) + return pos.clone().sub(origin) + } + + toGlobalPos(pos: Vector3) { + const origin = asVect3(this.bounds.min.clone()) + return origin.add(pos) + } + + containsPoint(pos: Vector3) { + return this.bounds.containsPoint(asVect2(pos)) + // return ( + // blockPos.x >= this.bounds.min.x && + // blockPos.z >= this.bounds.min.z && + // blockPos.x < this.bounds.max.x && + // blockPos.z < this.bounds.max.z + // ) + } + + // abstract get chunkIds(): ChunkId[] + // abstract toChunks(): any } /** * PatchesMap base class */ export class PatchesMapBase { - patchDimensions: Vector2 - constructor(patchDim: Vector2) { - this.patchDimensions = patchDim - } - - getPatchRange(bbox: Box2) { - const rangeMin = getPatchId(bbox.min, this.patchDimensions) - const rangeMax = patchUpperId(bbox.max, this.patchDimensions)//.addScalar(1) - const patchRange = new Box2(rangeMin, rangeMax) - return patchRange - } - - getPatchIds(bbox: Box2) { - const patchIds = [] - const patchRange = this.getPatchRange(bbox) - // iter elements on computed range - const { min, max } = patchRange - for (let { x } = min; x <= max.x; x++) { - for (let { y } = min; y <= max.y; y++) { - patchIds.push(new Vector2(x, y)) - } - } - return patchIds - } - - getRoundedBox(bbox: Box2) { - const { min, max } = this.getPatchRange(bbox) - min.multiply(this.patchDimensions) - max.multiply(this.patchDimensions) - const extBbox = new Box2(min, max) - return extBbox - } - /** - * Merges all patches as single data container - */ - asMergedContainer() { - + patchDimensions: Vector2 + constructor(patchDim: Vector2) { + this.patchDimensions = patchDim + } + + getPatchRange(bbox: Box2) { + const rangeMin = getPatchId(bbox.min, this.patchDimensions) + const rangeMax = patchUpperId(bbox.max, this.patchDimensions) // .addScalar(1) + const patchRange = new Box2(rangeMin, rangeMax) + return patchRange + } + + getPatchIds(bbox: Box2) { + const patchIds = [] + const patchRange = this.getPatchRange(bbox) + // iter elements on computed range + const { min, max } = patchRange + for (let { x } = min; x <= max.x; x++) { + for (let { y } = min; y <= max.y; y++) { + patchIds.push(new Vector2(x, y)) + } } + return patchIds + } + + getRoundedBox(bbox: Box2) { + const { min, max } = this.getPatchRange(bbox) + min.multiply(this.patchDimensions) + max.multiply(this.patchDimensions) + const extBbox = new Box2(min, max) + return extBbox + } + + /** + * Merges all patches as single data container + */ + asMergedContainer() {} } diff --git a/src/datacontainers/EntityChunk.ts b/src/datacontainers/EntityChunk.ts index 6f95423..4dc0948 100644 --- a/src/datacontainers/EntityChunk.ts +++ b/src/datacontainers/EntityChunk.ts @@ -1,101 +1,99 @@ -import { Box3, Vector3, Vector2, Box2 } from "three" -import { EntityData, PatchBlock } from "../common/types" -import { asBox2, asVect2 } from "../common/utils" -import { BlockType, WorldUtils } from "../index" -import { TreeGenerators } from "../tools/TreeGenerator" -import { WorldChunk } from "./WorldChunk" +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 + 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) - } + 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 + return chunkBox || entityBox } export class EntityChunk extends WorldChunk { - entityData: EntityData + entityData: EntityData - constructor(entityData: EntityData, customChunkBox?: Box3) { - super(adjustChunkBox(entityData.bbox, customChunkBox)) - this.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 - } - } + 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 + } } + 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 - } + 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 + 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 - } -} \ No newline at end of file + 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/BlocksPatch.ts b/src/datacontainers/GroundPatch.ts similarity index 70% rename from src/datacontainers/BlocksPatch.ts rename to src/datacontainers/GroundPatch.ts index 30d9602..1e51724 100644 --- a/src/datacontainers/BlocksPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -1,22 +1,19 @@ import { Box2, Vector2, Vector3 } from 'three' +import { Block, PatchBlock, PatchKey } from '../common/types' import { - Block, - PatchBlock, - PatchKey, -} from '../common/types' -import { - patchBoxFromKey, parsePatchKey, parseThreeStub, asVect3, asVect2, - serializePatchId, + asBox2, } from '../common/utils' +import { WorldComputeProxy, WorldConf } from '../index' import { BlockType } from '../procgen/Biome' -import { WorldConf } from '../index' import { DataContainer } from './DataContainers' +import { EntityChunk } from './EntityChunk' +import { WorldChunk } from './WorldChunk' export enum BlockMode { DEFAULT, @@ -43,31 +40,21 @@ const BlockDataBitAllocation = { mode: 3, // support for 8 different block mode } -export type BlockIteratorRes = IteratorResult +// for debug use only +const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { + return WorldConf.debug.patchBordersHighlightColor && + (localPos.x === 1 || localPos.z === 1) + ? WorldConf.debug.patchBordersHighlightColor + : blockType +} -const getDefaultPatchDim = () => - new Vector2(WorldConf.patchSize, WorldConf.patchSize) +export type BlockIteratorRes = IteratorResult -const parseBoundsOrKeyInput = (patchBoundsOrKey: Box2 | string) => { - const bounds = patchBoundsOrKey instanceof Box2 - ? patchBoundsOrKey.clone() - : patchBoxFromKey(patchBoundsOrKey, getDefaultPatchDim()) - return bounds -} +export class GroundPatch extends DataContainer { + rawData: Uint32Array -export class BlocksPatch extends DataContainer { - rawData!: Uint32Array - margin = 0 - key = '' // needed for patch export - patchId: Vector2 | undefined - - constructor(patchBoundsOrKey: Box2 | PatchKey = new Box2(), margin = 1) { - super(parseBoundsOrKeyInput(patchBoundsOrKey)) - const patchId = typeof patchBoundsOrKey === "string" ? parsePatchKey(patchBoundsOrKey) : null - if (patchId) { - this.id = patchId - } - this.margin = margin + constructor(boundsOrPatchKey: Box2 | PatchKey = new Box2(), margin = 1) { + super(boundsOrPatchKey, margin) this.rawData = new Uint32Array(this.extendedDims.x * this.extendedDims.y) } @@ -76,32 +63,6 @@ export class BlocksPatch extends DataContainer { this.rawData = new Uint32Array(this.extendedDims.x * this.extendedDims.y) } - get id() { - return this.patchId - } - - set id(patchId: Vector2 | undefined) { - this.patchId = patchId - this.key = serializePatchId(patchId) - } - - get extendedBounds() { - return this.bounds.clone().expandByScalar(this.margin) - } - - get extendedDims() { - return this.extendedBounds.getSize(new Vector2()) - } - - get localBox() { - const localBox = new Box2(new Vector2(0), this.dimensions.clone()) - return localBox - } - - get localExtendedBox() { - return this.localBox.expandByScalar(this.margin) - } - decodeBlockData(rawData: number): BlockData { const shift = BlockDataBitAllocation const level = @@ -149,15 +110,18 @@ export class BlocksPatch extends DataContainer { ) return local ? new Box2(rangeMin, rangeMax) - : new Box2(asVect2(this.toLocalPos(asVect3(rangeMin))), - asVect2(this.toLocalPos(asVect3(rangeMax)))) + : new Box2( + asVect2(this.toLocalPos(asVect3(rangeMin))), + asVect2(this.toLocalPos(asVect3(rangeMax))), + ) } override getIndex(localPos: Vector2 | Vector3) { localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) return ( (localPos.x + this.margin) * this.extendedDims.y + - localPos.y + this.margin + localPos.y + + this.margin ) } @@ -241,7 +205,7 @@ export class BlocksPatch extends DataContainer { const { bounds, rawData } = this const patchStub: PatchStub = { bounds, - rawData + rawData, } if (this.key && this.key !== '') patchStub.key = this.key return patchStub @@ -256,6 +220,53 @@ export class BlocksPatch extends DataContainer { return this } + async fillGroundData() { + const stub: PatchStub = await WorldComputeProxy.instance.bakeGroundPatch( + this.key || this.bounds, + ) + this.rawData.set(stub.rawData) + // this.bounds.min = min + // this.bounds.max = max + // this.bounds.getSize(this.dimensions) + } + + fillChunk(worldChunk: WorldChunk) { + const blocks = this.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 || []) + } + } + + // TODO rename mergeWithEntities + mergeEntityVoxels(entityChunk: EntityChunk, worldChunk: WorldChunk) { + // return overlapping blocks between entity and container + const patchBlocksIter = this.iterBlocksQuery(asBox2(entityChunk.chunkBox)) + // 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 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.writeBlock(block.localPos, block.data, bufferData) + } + } + // getBlocksRow(zRowIndex: number) { // const rowStart = zRowIndex * this.dimensions.y // const rowEnd = rowStart + this.dimensions.x diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts index db2d772..5e34c01 100644 --- a/src/datacontainers/GroundPatchesMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -2,7 +2,7 @@ import { Box2, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' import { asVect3 } from '../common/utils' -import { BlocksPatch, WorldComputeProxy, WorldConf } from '../index' +import { GroundPatch, WorldComputeProxy, WorldConf } from '../index' import { PatchesMap } from './PatchesMap' @@ -12,7 +12,7 @@ const getDefaultPatchDim = () => /** * Blocks cache */ -export class CacheContainer extends PatchesMap { +export class CacheContainer extends PatchesMap { static cachePowRadius = 2 static cacheSize = WorldConf.patchSize * 5 // eslint-disable-next-line no-use-before-define @@ -70,13 +70,18 @@ export class CacheContainer extends PatchesMap { getOverlappingPatches(inputBounds: Box2) { const overlappingBounds = (bounds1: Box2, bounds2: Box2) => - !(bounds1.max.x <= bounds2.min.x || bounds1.min.x >= bounds2.max.x || bounds1.max.y <= bounds2.min.y || bounds1.min.y >= bounds2.max.y); + !( + bounds1.max.x <= bounds2.min.x || + bounds1.min.x >= bounds2.max.x || + bounds1.max.y <= bounds2.min.y || + bounds1.min.y >= bounds2.max.y + ) return this.availablePatches.filter(patch => overlappingBounds(patch.bounds, inputBounds), ) } - getNearPatches(patch: BlocksPatch) { + getNearPatches(patch: GroundPatch) { const dim = patch.dimensions const patchCenter = patch.bounds.getCenter(new Vector2()) const minX = patchCenter.clone().add(new Vector3(-dim.x, 0)) @@ -97,9 +102,9 @@ export class CacheContainer extends PatchesMap { maxXminZ, maxXmaxZ, ] - const patchNeighbours: BlocksPatch[] = neighboursCenters + const patchNeighbours: GroundPatch[] = neighboursCenters .map(patchCenter => this.findPatch(asVect3(patchCenter))) - .filter(patch => patch) as BlocksPatch[] + .filter(patch => patch) as GroundPatch[] return patchNeighbours } diff --git a/src/datacontainers/PatchesMap.ts b/src/datacontainers/PatchesMap.ts index 4e4ac3a..0b61f14 100644 --- a/src/datacontainers/PatchesMap.ts +++ b/src/datacontainers/PatchesMap.ts @@ -44,10 +44,6 @@ export class PatchesMap> extends PatchesMapBase { return Object.keys(this.patchLookup) } - get chunkIds() { - return this.availablePatches.map(patch => patch.chunkIds).flat() - } - get availablePatches() { return Object.values(this.patchLookup).filter(val => val) as T[] } @@ -59,7 +55,7 @@ export class PatchesMap> extends PatchesMapBase { } // autoFill(fillingVal=0){ - // this.patchKeys.forEach(key=>this.patchLookup[key] = new BlocksPatch(key)) + // this.patchKeys.forEach(key=>this.patchLookup[key] = new GroundPatch(key)) // this.availablePatches.forEach(patch=>patch.iterOverBlocks) // } @@ -69,7 +65,7 @@ export class PatchesMap> extends PatchesMapBase { .filter(patch => this.patchLookup[patch.key] !== undefined) .forEach(patch => { this.patchLookup[patch.key] = cloneObjects - ? (patch.duplicate() as T) + ? patch // (patch.duplicate() as T) : patch // min.y = Math.min(patch.bbox.min.y, min.y) // max.y = Math.max(patch.bbox.max.y, max.y) @@ -89,13 +85,6 @@ export class PatchesMap> extends PatchesMapBase { return patchKeysDiff } - toChunks() { - const exportedChunks = this.availablePatches - .map(patch => patch.toChunks()) - .flat() - return exportedChunks - } - findPatch(blockPos: Vector3) { const res = this.availablePatches.find(patch => patch.containsPoint(blockPos), diff --git a/src/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 1f0887b..4c24d9f 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -5,6 +5,7 @@ import { ProcLayer } from '../procgen/ProcLayer' import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' import { WorldConf } from '../index' + import { PatchesMapBase } from './DataContainers' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' @@ -90,13 +91,13 @@ export class PseudoDistributionMap extends PatchesMapBase { for (const patchId of patchIds) { const offset = patchId.clone().multiply(this.patchDimensions) const localTestBox = testBox.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) - } + // 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) } + } } const spawnedEntities = overlappingEntities.filter(entityPos => this.hasSpawned(entityPos, spawnProbabilityOverride?.(entityPos)), diff --git a/src/datacontainers/WorldChunk.ts b/src/datacontainers/WorldChunk.ts index 75318d9..31acd87 100644 --- a/src/datacontainers/WorldChunk.ts +++ b/src/datacontainers/WorldChunk.ts @@ -1,73 +1,77 @@ -import { Box3, MathUtils, Vector3 } from "three" -import { ChunkKey } from "../common/types" -import { ChunkFactory } from "../index" -import { BlockData, BlockMode } from "./BlocksPatch" +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 + box: Box3 + data: Uint16Array } export type WorldChunkStub = { - key: ChunkKey - data: Uint16Array | null + key: ChunkKey + data: Uint16Array | null } export class WorldChunk { - chunkBox: Box3 - chunkData: Uint16Array + 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) - } + 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)) + 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 + let written_blocks_count = 0 - const level = MathUtils.clamp( - blockLocalPos.y + bufferOver.length, - chunkBox.min.y, - chunkBox.max.y, + 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 blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type + 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, ) - 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 blockType = buff_index > 0 ? bufferOver[buff_index] : blockData.type - 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 + blockType && written_blocks_count++ + } + h-- + buff_index-- + depth++ } -} \ No newline at end of file + return written_blocks_count + } +} diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 8fdb043..89ca9a0 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -1,12 +1,18 @@ import { Box2, Vector2, Vector3 } from 'three' -import { Block, PatchBlock } from '../common/types' +import { Block, EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' -import { BlockType, DataContainer, GroundPatch, ProcLayer, WorldCompute, WorldConf } from '../index' - +import { + BlockType, + DataContainer, + GroundPatch, + ProcLayer, + WorldComputeProxy, + WorldConf, +} from '../index' import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' -import { BlockMode, BlocksPatch, PatchStub } from '../datacontainers/BlocksPatch' import { findBoundingBox } from '../common/math' +import { BlockMode, PatchStub } from '../datacontainers/GroundPatch' export enum BoardBlockType { FLAT = 0, @@ -15,23 +21,22 @@ export enum BoardBlockType { } export type BoardBlock = { - blockType: BlockType, - subtype: BoardBlockType, + blockType: BlockType + subtype: BoardBlockType } export type BoardInputParams = { - radius: number, - thickness: number, + radius: number + thickness: number } export type BoardInput = BoardInputParams & { center: Vector3 } export type BoardOutputData = { - bounds: Box2, + bounds: Box2 data: BoardBlock[] } - // map block type to board block type const boardBlockTypeMapper = (blockType: BlockType) => { switch (blockType) { @@ -44,8 +49,7 @@ const boardBlockTypeMapper = (blockType: BlockType) => { } } -export type BoardStub = PatchStub & -{ +export type BoardStub = PatchStub & { input: BoardInput output: BoardOutputData } @@ -74,53 +78,79 @@ const holesDistParams = { * - fill with ground blocks * - override original blocks and adjust external bounds * - add board entities (trimmed trees, holes) - * + * * Board data export format: * - bounds * - data as array of block's type */ export class BoardContainer extends GroundPatch { - // static prevContainerBounds: Box2 | undefined // static singleton: BoardContainer // static get instance(){ // return this.singleton // } - static holesDistribution = new PseudoDistributionMap(undefined, holesDistParams) - static holesMapDistribution = new ProcLayer("holesMap") + static holesDistribution = new PseudoDistributionMap( + undefined, + holesDistParams, + ) + + static holesMapDistribution = new ProcLayer('holesMap') // static startPosDistribution = new PseudoDistributionMap(undefined, startPosDistParams) // board input params input: BoardInput = { center: new Vector3(), radius: 0, - thickness: 0 + thickness: 0, } // board output data output: BoardOutputData = { bounds: new Box2(), - data: [] + data: [], + } + + entities: { + obstacles: EntityData[] + holes: EntityData[] + } = { + obstacles: [], + holes: [], } - // swapContainer!: BlocksPatch //Uint32Array + // swapContainer!: GroundPatch //Uint32Array /** - * - * @param center - * @param radius + * + * @param center + * @param radius * @param previousBounds // used for handling previous board removal - * @returns + * @returns */ - static getInitialBounds = (center: Vector3, radius: number, previousBounds?: Box2) => { + static getInitialBounds = ( + center: Vector3, + radius: number, + previousBounds?: Box2, + ) => { // const previousBounds = BoardContainer.prevContainerBounds - const defaultBounds = new Box2() - .setFromCenterAndSize(asVect2(center), new Vector2(radius, radius) - .multiplyScalar(2)) + const defaultBounds = new Box2().setFromCenterAndSize( + asVect2(center), + new Vector2(radius, radius).multiplyScalar(2), + ) return previousBounds ? defaultBounds.union(previousBounds) : defaultBounds } - constructor(boardCenter = new Vector3(), boardParams?: BoardInputParams, lastBoardBounds?: Box2) { - super(BoardContainer.getInitialBounds(boardCenter, boardParams?.radius || 0, lastBoardBounds)) + constructor( + boardCenter = new Vector3(), + boardParams?: BoardInputParams, + lastBoardBounds?: Box2, + ) { + super( + BoardContainer.getInitialBounds( + boardCenter, + boardParams?.radius || 0, + lastBoardBounds, + ), + ) const { input } = this input.center = boardCenter.clone().floor() input.radius = boardParams?.radius || input.radius @@ -155,7 +185,9 @@ export class BoardContainer extends GroundPatch { blockData.mode = BlockMode.BOARD_CONTAINER blockData.level = this.input.center.y - blockData.type = this.isWithinBoard(block.pos) ? blockData.type : BlockType.DBG_ORANGE + blockData.type = this.isWithinBoard(block.pos) + ? blockData.type + : BlockType.DBG_ORANGE return blockData } @@ -174,18 +206,17 @@ export class BoardContainer extends GroundPatch { /** * Override original ground blocks with board blocks * and adjust final board bounds - * @returns + * @returns */ shapeBoard() { const { center } = this.input // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) - const tempContainer = new BlocksPatch(this.bounds) + const tempContainer = new GroundPatch(this.bounds) const finalBounds = new Box2(asVect2(center), asVect2(center)) - const boardBlocks = this.iterBlocksQuery(undefined, false); + const boardBlocks = this.iterBlocksQuery(undefined, false) // const boardBlocks = this.iterBoardBlock() for (const block of boardBlocks) { - // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) if (this.isWithinBoard(block.pos)) { tempContainer.writeBlockData(block.index, this.overrideBlockData(block)) @@ -196,14 +227,29 @@ export class BoardContainer extends GroundPatch { const bounds = new Box2(asVect2(center), asVect2(center)) bounds.expandByVector(new Vector2(1, 1).multiplyScalar(10)) const finalBoardContainer = new GroundPatch(finalBounds) - DataContainer.copySourceOverTargetContainer(tempContainer, finalBoardContainer) + DataContainer.copySourceOverTargetContainer( + tempContainer, + finalBoardContainer, + ) return finalBoardContainer } - smoothEdges() { } + async populateEntities() { + // query external entities (trees) from world-compute + const trees = await WorldComputeProxy.instance.queryEntities(this.bounds) + // query local entities (holes) + // const holes = this.queryLocalEntities(boardContainer, BoardContainer.holesDistribution) + this.entities.obstacles.push(...trees) + } - getBoardEntities(boardContainer: BlocksPatch, distMap: PseudoDistributionMap, entityRadius = 2) { - const intersectsEntity = (testRange: Box2, entityPos: Vector2) => testRange.distanceToPoint(entityPos) <= entityRadius + // perform local query + queryLocalEntities( + boardContainer: GroundPatch, + distMap: PseudoDistributionMap, + entityRadius = 2, + ) { + const intersectsEntity = (testRange: Box2, entityPos: Vector2) => + testRange.distanceToPoint(entityPos) <= entityRadius const spawnLocs = distMap.querySpawnLocations( boardContainer.bounds, intersectsEntity, @@ -215,15 +261,13 @@ export class BoardContainer extends GroundPatch { const block = boardContainer.getBlock(startPos, false) return block }) - .filter( - block => block && this.isWithinBoard(block.pos), - ) as PatchBlock[] + .filter(block => block && this.isWithinBoard(block.pos)) as PatchBlock[] // TODO prune entities spawning over existing entities return entities } // Moved to SDK - // genStartPositions(boardContainer: BlocksPatch, otherEntities: PatchBlock[]) { + // genStartPositions(boardContainer: GroundPatch, otherEntities: PatchBlock[]) { // const startPositions = this.getBoardEntities(boardContainer, BoardContainer.startPosDistribution) // WorldConf.debug.boardStartPosHighlightColor && // startPositions.forEach(block => { @@ -235,14 +279,17 @@ export class BoardContainer extends GroundPatch { // return startPositions // } - boardSplit(boardContainer: BlocksPatch) { + boardSplit(boardContainer: GroundPatch) { const { center } = this.input - const boardBlocks = boardContainer.iterBlocksQuery(undefined, false); + const boardBlocks = boardContainer.iterBlocksQuery(undefined, false) const dims = boardContainer.bounds.getSize(new Vector2()) - const check = (pos: Vector3) => dims.x < dims.y ? pos.z < center.z : pos.x < center.x + const check = (pos: Vector3) => + dims.x < dims.y ? pos.z < center.z : pos.x < center.x for (const block of boardBlocks) { if (this.isWithinBoard(block.pos)) { - block.data.type = check(block.pos) ? BlockType.DBG_ORANGE : BlockType.DBG_GREEN + block.data.type = check(block.pos) + ? BlockType.DBG_ORANGE + : BlockType.DBG_GREEN boardContainer.writeBlockData(block.index, block.data) } } @@ -252,27 +299,36 @@ export class BoardContainer extends GroundPatch { return BoardContainer.holesMapDistribution.eval(testPos) < 0.15 } - digGroundHole(holeBlock: Block, boardContainer: BlocksPatch) { + digGroundHole(holeBlock: Block, boardContainer: GroundPatch) { holeBlock.data.type = BlockType.BOARD_HOLE holeBlock.data.level -= 1 // dig hole in the ground holeBlock.data.mode = BlockMode.DEFAULT boardContainer.setBlock(holeBlock.pos, holeBlock.data) } - getHolesMonoBlocks(boardContainer: BlocksPatch) { - const holesSingleBlocks = this.getBoardEntities(boardContainer, BoardContainer.holesDistribution) - .map(({ pos, data }) => ({ pos, data })) + getHolesMonoBlocks(boardContainer: GroundPatch) { + const holesSingleBlocks = this.queryLocalEntities( + boardContainer, + BoardContainer.holesDistribution, + ).map(({ pos, data }) => ({ pos, data })) return holesSingleBlocks } - getHolesAreas(boardContainer: BlocksPatch, forbiddenBlocks: Block[]) { + getHolesAreas(boardContainer: GroundPatch, forbiddenBlocks: Block[]) { const forbiddenPos = forbiddenBlocks.map(({ pos }) => asVect2(pos)) - const holesMono = this.getBoardEntities(boardContainer, BoardContainer.holesDistribution) + const holesMono = this.queryLocalEntities( + boardContainer, + BoardContainer.holesDistribution, + ) const holesMulti: PatchBlock[] = [] // for each monoblock hole, find maximum bounding box around holesMono.forEach(hole => { const pos = asVect2(hole.pos) - const holeBounds = findBoundingBox(pos, forbiddenPos, boardContainer.bounds); + const holeBounds = findBoundingBox( + pos, + forbiddenPos, + boardContainer.bounds, + ) const holeBlocks = boardContainer.iterBlocksQuery(holeBounds) for (const block of holeBlocks) { holesMulti.push(block) @@ -281,32 +337,36 @@ export class BoardContainer extends GroundPatch { return holesMulti.map(({ pos, data }) => ({ pos, data }) as Block) } - getHolesAreasBis(boardContainer: BlocksPatch, forbiddenBlocks: Block[]) { + getHolesAreasBis(boardContainer: GroundPatch, forbiddenBlocks: Block[]) { // prevent holes from spreading over forbidden blocks - const isForbiddenPos = (testPos: Vector3) => !!forbiddenBlocks.find(block => block.pos.equals(testPos)) + const isForbiddenPos = (testPos: Vector3) => + !!forbiddenBlocks.find(block => block.pos.equals(testPos)) const blocks = boardContainer.iterBlocksQuery() const holes: Block[] = [] for (const block of blocks) { const testPos = block.pos - if (this.isWithinBoard(testPos) && this.isGroundHole(testPos) && !isForbiddenPos(testPos)) { + if ( + this.isWithinBoard(testPos) && + this.isGroundHole(testPos) && + !isForbiddenPos(testPos) + ) { holes.push(block) } } return holes.map(({ pos, data }) => ({ pos, data }) as Block) } - trimTrees(boardContainer: BlocksPatch) { - const treeEntities = WorldCompute.queryEntities(boardContainer.bounds) - const trunks = treeEntities.map(entity => { - const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = boardContainer.getBlock( - entityCenter, - false, - ) - entityCenter.y = entity.bbox.min.y - return entityCenterBlock - }) - .filter(trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos)) as PatchBlock[] + trimTrees(boardContainer: GroundPatch) { + const trunks = this.entities.obstacles + .map(entity => { + const entityCenter = entity.bbox.getCenter(new Vector3()) + const entityCenterBlock = boardContainer.getBlock(entityCenter, false) + entityCenter.y = entity.bbox.min.y + return entityCenterBlock + }) + .filter( + trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos), + ) as PatchBlock[] trunks.forEach(trunkBlock => { trunkBlock.data.type = BlockType.TREE_TRUNK @@ -325,6 +385,7 @@ export class BoardContainer extends GroundPatch { const holes: Block[] = this.getHolesAreasBis(outputContainer, obstacles) holes.forEach(block => this.digGroundHole(block, outputContainer)) this.output.bounds = outputContainer.bounds + DataContainer.copySourceOverTargetContainer(outputContainer, this) return outputContainer } @@ -337,13 +398,12 @@ export class BoardContainer extends GroundPatch { } override toStub(): BoardStub { - const outputContainer = this.getOutputContainer() - DataContainer.copySourceOverTargetContainer(outputContainer, this) + this.getOutputContainer() const { input, output } = this const boardStub: BoardStub = { ...super.toStub(), input, - output + output, } return boardStub } @@ -355,14 +415,14 @@ export class BoardContainer extends GroundPatch { const blockType = block.data.type const boardBlock: BoardBlock = { blockType, - subtype: boardBlockTypeMapper(blockType) + subtype: boardBlockTypeMapper(blockType), } this.output.data.push(boardBlock) } const { bounds, data } = this.output const boardData: BoardOutputData = { bounds, - data + data, } // optional raw data export // return includeRawData ? toStub() : boardData diff --git a/src/feats/GroundPatch.ts b/src/feats/GroundPatch.ts deleted file mode 100644 index 95d0d1e..0000000 --- a/src/feats/GroundPatch.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Vector3 } from "three"; -import { asBox2 } from "../common/utils"; -import { BlockType, WorldCompute, WorldConf } from "../index"; -import { BlocksPatch } from "../datacontainers/BlocksPatch"; -import { EntityChunk } from "../datacontainers/EntityChunk"; -import { WorldChunk } from "../datacontainers/WorldChunk"; - -// for debug use only -const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return WorldConf.debug.patchBordersHighlightColor && (localPos.x === 1 || localPos.z === 1) - ? WorldConf.debug.patchBordersHighlightColor - : blockType -} - -export class GroundPatch extends BlocksPatch { - fill() { - const { min, max } = this.bounds - const blocks = this.iterBlocksQuery(undefined, false) - const level = { - min: 512, - max: 0 - } - let blockIndex = 0 - for (const block of blocks) { - const blockData = WorldCompute.computeGroundBlock(block.pos) - level.min = Math.min(min.y, blockData.level) - level.max = Math.max(max.y, blockData.level) - this.writeBlockData(blockIndex, blockData) - blockIndex++ - } - // this.bounds.min = min - // this.bounds.max = max - // this.bounds.getSize(this.dimensions) - } - - fillChunk(worldChunk: WorldChunk) { - const blocks = this.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 || []) - } - } - - // TODO rename mergeWithEntities - mergeEntityVoxels(entityChunk: EntityChunk, worldChunk: WorldChunk) { - // return overlapping blocks between entity and container - const patchBlocksIter = this.iterBlocksQuery(asBox2(entityChunk.chunkBox)) - // 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 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.writeBlock( - block.localPos, - block.data, - bufferData, - ) - } - } -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 7647c98..1d81e0a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,11 +4,10 @@ export { Heightmap } from './procgen/Heightmap' export { NoiseSampler } from './procgen/NoiseSampler' export { ProcLayer } from './procgen/ProcLayer' export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' -export { GroundPatch } from './feats/GroundPatch' export { BoardContainer } from './feats/BoardContainer' export { EntityType } from './common/types' +export { BlockMode, GroundPatch } from './datacontainers/GroundPatch' export { PatchesMap } from './datacontainers/PatchesMap' -export { BlockMode, BlocksPatch } from './datacontainers/BlocksPatch' export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' export { ChunkFactory } from './tools/ChunkFactory' export { WorldComputeProxy } from './api/WorldComputeProxy' diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index e3a6dcc..3552b95 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -1,19 +1,21 @@ -import { BlockType } from "../index" +import { BlockType } from '../index' export class WorldConf { static patchPowSize = 6 // as a power of two static get patchSize() { return Math.pow(2, this.patchPowSize) } + // max cache radius as a power of two static cachePowLimit = 2 // 4 => 16 patches radius static get cacheLimit() { return Math.pow(2, this.cachePowLimit) } + static defaultDistMapPeriod = 4 * WorldConf.patchSize static debug = { patchBordersHighlightColor: BlockType.NONE, boardStartPosHighlightColor: BlockType.NONE, - boardStartSideColoring: false + boardStartSideColoring: false, } } diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index ef68da8..135de7d 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -6,79 +6,79 @@ import { Box2, Vector2 } from 'three' * Self repeating seamless pattern */ export class BlueNoisePattern { - bbox: Box2 - params - elements: Vector2[] = [] + bbox: Box2 + params + elements: Vector2[] = [] - constructor(bbox: Box2, distParams: any) { - this.bbox = bbox - this.params = distParams - this.populate() - } + constructor(bbox: Box2, distParams: any) { + this.bbox = bbox + this.params = distParams + this.populate() + } - get dimensions() { - return this.bbox.getSize(new Vector2()) - } + get dimensions() { + return this.bbox.getSize(new Vector2()) + } - // populate with discrete elements using relative pos - populate() { - const { dimensions, params } = this - const { aleaSeed } = this.params - const prng = alea(aleaSeed || '') - const p = new PoissonDiskSampling( - { - shape: [dimensions.x, dimensions.y], - ...params, - }, - prng, - ) - this.elements = p - .fill() - .map(point => new Vector2(point[0] as number, point[1] as number).round()) - this.makeSeamless() - } + // populate with discrete elements using relative pos + populate() { + const { dimensions, params } = this + const { aleaSeed } = this.params + const prng = alea(aleaSeed || '') + const p = new PoissonDiskSampling( + { + shape: [dimensions.x, dimensions.y], + ...params, + }, + prng, + ) + this.elements = p + .fill() + .map(point => new Vector2(point[0] as number, point[1] as number).round()) + this.makeSeamless() + } - // make seamless repeatable pattern - makeSeamless() { - const { dimensions, params } = this - const radius = params.minDistance / 2 - const edgePoints = this.elements - .map(point => { - const pointCopy = point.clone() - if (point.x - radius < 0) { - pointCopy.x += dimensions.x - } else if (point.x + radius > dimensions.x) { - pointCopy.x -= dimensions.x - } - if (point.y - radius < 0) { - pointCopy.y += dimensions.y - } else if (point.y + radius > dimensions.y) { - pointCopy.y -= dimensions.y - } - return pointCopy.round().equals(point) ? null : pointCopy - }) - .filter(pointCopy => pointCopy) - edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) - } + // make seamless repeatable pattern + makeSeamless() { + const { dimensions, params } = this + const radius = params.minDistance / 2 + const edgePoints = this.elements + .map(point => { + const pointCopy = point.clone() + if (point.x - radius < 0) { + pointCopy.x += dimensions.x + } else if (point.x + radius > dimensions.x) { + pointCopy.x -= dimensions.x + } + if (point.y - radius < 0) { + pointCopy.y += dimensions.y + } else if (point.y + radius > dimensions.y) { + pointCopy.y -= dimensions.y + } + return pointCopy.round().equals(point) ? null : pointCopy + }) + .filter(pointCopy => pointCopy) + edgePoints.forEach(edgePoint => edgePoint && this.elements.push(edgePoint)) + } - getPatchOrigin(patchId: Vector2) { - return patchId.clone().multiply(this.dimensions) - } + getPatchOrigin(patchId: Vector2) { + return patchId.clone().multiply(this.dimensions) + } - toPatchLocalPos(pos: Vector2, patchId: Vector2) { - return pos.clone().sub(this.getPatchOrigin(patchId)) - } + toPatchLocalPos(pos: Vector2, patchId: Vector2) { + return pos.clone().sub(this.getPatchOrigin(patchId)) + } - toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { - return relativePos.clone().add(this.getPatchOrigin(patchId)) - } + toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { + return relativePos.clone().add(this.getPatchOrigin(patchId)) + } - // DO NOT USE SLOW - *iterPatchElements(patchOffset: Vector2) { - // relative to global pos conv - for (const relativePos of this.elements) { - const pos = this.toPatchGlobalPos(relativePos, patchOffset) - yield pos - } + // DO NOT USE SLOW + *iterPatchElements(patchOffset: Vector2) { + // relative to global pos conv + for (const relativePos of this.elements) { + const pos = this.toPatchGlobalPos(relativePos, patchOffset) + yield pos } + } } diff --git a/src/procgen/WorldEntities.ts b/src/procgen/WorldEntities.ts index eccd123..ff4d06c 100644 --- a/src/procgen/WorldEntities.ts +++ b/src/procgen/WorldEntities.ts @@ -1,58 +1,60 @@ -import { Box2, Box3, Vector2 } from "three" -import { Vector3 } from "three/src/math/Vector3" -import { EntityData, EntityType } from "../common/types" -import { PseudoDistributionMap } from "../index" +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 { - static singleton: WorldEntities - static get instance() { - this.singleton = this.singleton || new WorldEntities() - return this.singleton - } - entityDistributionMapping: Record + // eslint-disable-next-line no-use-before-define + static singleton: WorldEntities + static get instance() { + this.singleton = this.singleton || new WorldEntities() + return this.singleton + } - constructor() { - const treeDistribution = new PseudoDistributionMap() + entityDistributionMapping: Record - this.entityDistributionMapping = { - [EntityType.NONE]: treeDistribution, - [EntityType.TREE_APPLE]: treeDistribution, - [EntityType.TREE_PINE]: treeDistribution - } - } + constructor() { + const treeDistribution = new PseudoDistributionMap() - 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 + this.entityDistributionMapping = { + [EntityType.NONE]: treeDistribution, + [EntityType.TREE_APPLE]: treeDistribution, + [EntityType.TREE_PINE]: treeDistribution, } + } - 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) + 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, } -} \ No newline at end of file + return entityData // entityBox.translate(entityPos) + } +} diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 61c07f2..7364544 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,9 +1,8 @@ import { PatchId } from '../common/types' import { asVect3, chunkBoxFromId, serializeChunkId } from '../common/utils' -import { BlockMode } from '../datacontainers/BlocksPatch' import { EntityChunk } from '../datacontainers/EntityChunk' import { WorldChunk, WorldChunkStub } from '../datacontainers/WorldChunk' -import { BlockType, GroundPatch, WorldConf } from '../index' +import { BlockMode, BlockType, GroundPatch, WorldConf } from '../index' export class ChunkFactory { // eslint-disable-next-line no-use-before-define @@ -36,23 +35,28 @@ export class ChunkFactory { } return chunk_ids } + /** - * chunkify or chunksAssembly - * Assembles world building blocks (GroundPatch, EntityChunk) together - * to form final world chunk - */ + * chunkify or chunksAssembly + * Assembles world building blocks (GroundPatch, EntityChunk) together + * to form final world chunk + */ chunkify(patch: GroundPatch, patchEntities: EntityChunk[]) { - const patchChunkIds = patch.id ? ChunkFactory.default.genChunksIdsFromPatchId(patch.id) : [] + const patchChunkIds = patch.id + ? ChunkFactory.default.genChunksIdsFromPatchId(patch.id) + : [] const worldChunksStubs = patchChunkIds.map(chunkId => { const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) const worldChunk = new WorldChunk(chunkBox) // Ground pass patch.fillChunk(worldChunk) // Entities pass - patchEntities.forEach(entityChunk => patch.mergeEntityVoxels(entityChunk, worldChunk)) + patchEntities.forEach(entityChunk => + patch.mergeEntityVoxels(entityChunk, worldChunk), + ) const worldChunkStub: WorldChunkStub = { key: serializeChunkId(chunkId), - data: worldChunk.chunkData + data: worldChunk.chunkData, } return worldChunkStub }) From 5cafa360a94a5217b9d5f43ee9c4d767661b2134 Mon Sep 17 00:00:00 2001 From: etienne Date: Mon, 9 Sep 2024 15:03:46 +0000 Subject: [PATCH 42/45] fix: board export format, minor refactor --- src/datacontainers/DataContainers.ts | 10 +-- src/feats/BoardContainer.ts | 129 ++++++++++++++++----------- src/misc/WorldConfig.ts | 2 +- 3 files changed, 83 insertions(+), 58 deletions(-) diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 565e60d..9bdcb10 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -169,16 +169,16 @@ export class PatchesMapBase { this.patchDimensions = patchDim } - getPatchRange(bbox: Box2) { - const rangeMin = getPatchId(bbox.min, this.patchDimensions) - const rangeMax = patchUpperId(bbox.max, this.patchDimensions) // .addScalar(1) + getPatchRange(bounds: Box2) { + const rangeMin = getPatchId(bounds.min, this.patchDimensions) + const rangeMax = patchUpperId(bounds.max, this.patchDimensions) // .addScalar(1) const patchRange = new Box2(rangeMin, rangeMax) return patchRange } - getPatchIds(bbox: Box2) { + getPatchIds(bounds: Box2) { const patchIds = [] - const patchRange = this.getPatchRange(bbox) + const patchRange = this.getPatchRange(bounds) // iter elements on computed range const { min, max } = patchRange for (let { x } = min; x <= max.x; x++) { diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 89ca9a0..66d9167 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -1,4 +1,4 @@ -import { Box2, Vector2, Vector3 } from 'three' +import { Box2, Vector2, Vector3, Vector3Like } from 'three' import { Block, EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' @@ -14,15 +14,21 @@ import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' import { BlockMode, PatchStub } from '../datacontainers/GroundPatch' -export enum BoardBlockType { +export enum BlockCategory { FLAT = 0, HOLE = 1, OBSTACLE = 2, } export type BoardBlock = { - blockType: BlockType - subtype: BoardBlockType + type: BlockType + category: BlockCategory +} + +export type BoardOutputData = { + origin: Vector3Like + size: Vector2 + data: BoardBlock[] } export type BoardInputParams = { @@ -32,20 +38,15 @@ export type BoardInputParams = { export type BoardInput = BoardInputParams & { center: Vector3 } -export type BoardOutputData = { - bounds: Box2 - data: BoardBlock[] -} - // map block type to board block type -const boardBlockTypeMapper = (blockType: BlockType) => { +const blockTypeCategoryMapper = (blockType: BlockType) => { switch (blockType) { case BlockType.TREE_TRUNK: - return BoardBlockType.OBSTACLE + return BlockCategory.OBSTACLE case BlockType.BOARD_HOLE: - return BoardBlockType.HOLE + return BlockCategory.HOLE default: - return BoardBlockType.FLAT + return BlockCategory.FLAT } } @@ -104,10 +105,12 @@ export class BoardContainer extends GroundPatch { thickness: 0, } - // board output data - output: BoardOutputData = { - bounds: new Box2(), + // board data output + output: BoardOutputData & { overridingContainer: GroundPatch | undefined } = { + origin: new Vector3(), + size: new Vector2(), data: [], + overridingContainer: undefined, } entities: { @@ -159,6 +162,38 @@ export class BoardContainer extends GroundPatch { BoardContainer.holesMapDistribution.sampling.periodicity = 0.25 } + get overridingContainer() { + if (!this.output.overridingContainer) { + const overridingContainer = this.shapeBoard() + WorldConf.debug.boardSideSplitColoring && + this.boardSplit(overridingContainer) + // const boardEntitiesBlocks: Block[] = [] + const obstacles: Block[] = this.trimTrees(overridingContainer) + const holes: Block[] = this.getHolesAreasBis( + overridingContainer, + obstacles, + ) + holes.forEach(block => this.digGroundHole(block, overridingContainer)) + this.output.origin = asVect3( + overridingContainer.bounds.min, + this.input.center.y, + ) + overridingContainer.bounds.getSize(this.output.size) + this.output.overridingContainer = overridingContainer + DataContainer.copySourceOverTargetContainer(overridingContainer, this) + } + return this.output.overridingContainer + } + + async retrieveBoardData() { + await this.fillGroundData() + // populate external entities (trees) from world-compute + const trees = await WorldComputeProxy.instance.queryEntities(this.bounds) + // query local entities (holes) + // const holes = this.queryLocalEntities(boardContainer, BoardContainer.holesDistribution) + this.entities.obstacles.push(...trees) + } + isWithinBoard(blockPos: Vector3) { let isInsideBoard = false const { thickness, radius, center } = this.input @@ -234,14 +269,6 @@ export class BoardContainer extends GroundPatch { return finalBoardContainer } - async populateEntities() { - // query external entities (trees) from world-compute - const trees = await WorldComputeProxy.instance.queryEntities(this.bounds) - // query local entities (holes) - // const holes = this.queryLocalEntities(boardContainer, BoardContainer.holesDistribution) - this.entities.obstacles.push(...trees) - } - // perform local query queryLocalEntities( boardContainer: GroundPatch, @@ -283,8 +310,20 @@ export class BoardContainer extends GroundPatch { const { center } = this.input const boardBlocks = boardContainer.iterBlocksQuery(undefined, false) const dims = boardContainer.bounds.getSize(new Vector2()) - const check = (pos: Vector3) => - dims.x < dims.y ? pos.z < center.z : pos.x < center.x + const ratio = dims.y / dims.x + const projectionAxis = ratio > 1 ? new Vector3(dims.x, -dims.y) : dims // ratio > 1 ? new Vector2(0, 1) : new Vector2(1, 0) //dims.clone().normalize() //ratio > 1 ? new Vector2(0, 1) : new Vector2(1, 0) + const check = (pos: Vector3) => { + const diff = asVect2(pos.clone().sub(center)) + // const projection = pos2.dot(projectionAxis)X + const projection = diff.dot(projectionAxis) + return projection < 0 + // return ratio < 1 ? pos.z < center.z : pos.x < center.x + // if (Math.abs(ratio - 1) < 0.15){ + // console.log(`board dim ratio: ${ratio}`) + // return projection < 0 + // } + // else return ratio < 1 ? pos.z < center.z : pos.x < center.x + } for (const block of boardBlocks) { if (this.isWithinBoard(block.pos)) { block.data.type = check(block.pos) @@ -292,6 +331,10 @@ export class BoardContainer extends GroundPatch { : BlockType.DBG_GREEN boardContainer.writeBlockData(block.index, block.data) } + // else { + // block.data.type = BlockType.DBG_LIGHT + // boardContainer.writeBlockData(block.index, block.data) + // } } } @@ -377,28 +420,15 @@ export class BoardContainer extends GroundPatch { return trunks.map(({ pos, data }) => ({ pos, data }) as Block) } - getOutputContainer() { - const outputContainer = this.shapeBoard() - WorldConf.debug.boardStartSideColoring && this.boardSplit(outputContainer) - // const boardEntitiesBlocks: Block[] = [] - const obstacles: Block[] = this.trimTrees(outputContainer) - const holes: Block[] = this.getHolesAreasBis(outputContainer, obstacles) - holes.forEach(block => this.digGroundHole(block, outputContainer)) - this.output.bounds = outputContainer.bounds - DataContainer.copySourceOverTargetContainer(outputContainer, this) - return outputContainer - } - override fromStub(boardStub: BoardStub) { super.fromStub(boardStub) const { input, output } = boardStub this.input = input - this.output = output + this.output = { ...output, overridingContainer: undefined } return this } override toStub(): BoardStub { - this.getOutputContainer() const { input, output } = this const boardStub: BoardStub = { ...super.toStub(), @@ -409,23 +439,18 @@ export class BoardContainer extends GroundPatch { } exportBoardData() { - const outputContainer = this.getOutputContainer() - const boardBlocks = outputContainer.iterBlocksQuery() + const boardBlocks = this.overridingContainer.iterBlocksQuery() for (const block of boardBlocks) { const blockType = block.data.type + const blockCat = blockTypeCategoryMapper(blockType) const boardBlock: BoardBlock = { - blockType, - subtype: boardBlockTypeMapper(blockType), + type: blockType, + category: blockCat, } this.output.data.push(boardBlock) } - const { bounds, data } = this.output - const boardData: BoardOutputData = { - bounds, - data, - } - // optional raw data export - // return includeRawData ? toStub() : boardData - return boardData + const { origin, size, data } = this.output + const boardOutputData: BoardOutputData = { origin, size, data } + return boardOutputData } } diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index 3552b95..888160f 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -16,6 +16,6 @@ export class WorldConf { static debug = { patchBordersHighlightColor: BlockType.NONE, boardStartPosHighlightColor: BlockType.NONE, - boardStartSideColoring: false, + boardSideSplitColoring: false, } } From d600d06ab1f4f799b0c2078f9e875cd82fd94a8b Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 10 Sep 2024 14:44:48 +0000 Subject: [PATCH 43/45] refactor: misc --- src/common/utils.ts | 47 +++++++++-------- src/datacontainers/DataContainers.ts | 31 +++++++----- src/datacontainers/GroundPatch.ts | 45 ++++++++-------- src/feats/BoardContainer.ts | 76 ++++------------------------ src/misc/WorldConfig.ts | 10 ++-- src/procgen/BlueNoisePattern.ts | 4 +- 6 files changed, 85 insertions(+), 128 deletions(-) diff --git a/src/common/utils.ts b/src/common/utils.ts index c47aa90..756854b 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -64,12 +64,24 @@ const interpolatePoints = (p1: Vector2, p2: Vector2, t: number) => { } /** - * Direct neighbours e.g. + * Orthogonal or direct 2D neighbours e.g. + * - TOP/BOTTOM, + * - LEFT/RIGHT + */ +const directNeighbours2D = [ + Adjacent2dPos.left, + Adjacent2dPos.right, + Adjacent2dPos.top, + Adjacent2dPos.bottom +] + +/** + * Orthogonal or direct 3D neighbours e.g. * - FRONT/BACK, * - TOP/BOTTOM, * - LEFT/RIGHT */ -const AdjacentNeighbours3d = [ +const directNeighbours3D = [ Adjacent3dPos.xPy0z0, Adjacent3dPos.xMy0z0, // right, left Adjacent3dPos.x0yPz0, @@ -164,18 +176,16 @@ const getAdjacent3dCoords = (pos: Vector3, dir: Adjacent3dPos): Vector3 => { } } -const getAllNeighbours2dCoords = (pos: Vector2): Vector2[] => { - const neighbours = Object.values(Adjacent3dPos) - .filter(v => !isNaN(Number(v))) - .map(type => getAdjacent2dCoords(pos, type as number)) - return neighbours +const getNeighbours2D = (pos: Vector2, directNeighboursOnly = false): Vector2[] => { + const neighbours = directNeighboursOnly? directNeighbours2D : Object.values(Adjacent2dPos) + .filter(v => !isNaN(Number(v))) + return neighbours.map(type => getAdjacent2dCoords(pos, type as number)) } -const getAllNeighbours3dCoords = (pos: Vector3): Vector3[] => { - const neighbours = Object.values(Adjacent3dPos) +const getNeighbours3D = (pos: Vector3, directNeighboursOnly = false): Vector3[] => { + const neighbours = directNeighboursOnly ? directNeighbours3D : Object.values(Adjacent3dPos) .filter(v => !isNaN(Number(v))) - .map(type => getAdjacent3dCoords(pos, type as number)) - return neighbours + return neighbours.map(type => getAdjacent3dCoords(pos, type as number)) } const getPatchPoints = (patchBBox: Box3, clearY = true) => { @@ -274,10 +284,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 } @@ -360,11 +370,8 @@ export { clamp, findMatchingRange, interpolatePoints, - AdjacentNeighbours3d, - getAdjacent2dCoords, - getAdjacent3dCoords, - getAllNeighbours2dCoords, - getAllNeighbours3dCoords, + getNeighbours2D, + getNeighbours3D, bboxContainsPointXZ, getPatchPoints, parseThreeStub, diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 9bdcb10..c3140a9 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -91,7 +91,7 @@ export abstract class DataContainer { adjustOverlapMargins(overlap) for (let { x } = overlap.min; x < overlap.max.x; x++) { // const globalStartPos = new Vector3(x, 0, overlap.min.y) - const globalStartPos = new Vector3(x, 0, overlap.min.y) + const globalStartPos = new Vector2(x, overlap.min.y) const targetLocalStartPos = target.toLocalPos(globalStartPos) const sourceLocalStartPos = source.toLocalPos(globalStartPos) let targetIndex = target.getIndex(targetLocalStartPos) @@ -108,8 +108,7 @@ export abstract class DataContainer { } } - inLocalRange(localPos: Vector3 | Vector2) { - localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) + inLocalRange(localPos: Vector2) { return ( localPos.x >= 0 && localPos.x < this.dimensions.x && @@ -118,8 +117,7 @@ export abstract class DataContainer { ) } - inGlobalRange(globalPos: Vector3 | Vector2) { - globalPos = globalPos instanceof Vector2 ? globalPos : asVect2(globalPos) + inWorldRange(globalPos: Vector2) { return ( globalPos.x >= this.bounds.min.x && globalPos.x < this.bounds.max.x && @@ -128,26 +126,31 @@ export abstract class DataContainer { ) } - getIndex(localPos: Vector2 | Vector3) { - localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) + getIndex(localPos: Vector2) { return localPos.x * this.dimensions.y + localPos.y } + getLocalPosFromIndex(index: number) { + const y = index % this.dimensions.y + const x = Math.floor(index / this.dimensions.y) + return new Vector2(x, y) + } + // toLocalPos(pos: T): T // toGlobalPos(pos: T): T - toLocalPos(pos: Vector3) { - const origin = asVect3(this.bounds.min.clone()) + toLocalPos(pos: Vector2) { + const origin = this.bounds.min.clone() return pos.clone().sub(origin) } - toGlobalPos(pos: Vector3) { - const origin = asVect3(this.bounds.min.clone()) + toWorldPos(pos: Vector2) { + const origin = this.bounds.min.clone() return origin.add(pos) } - containsPoint(pos: Vector3) { - return this.bounds.containsPoint(asVect2(pos)) + containsPoint(pos: Vector2) { + return this.bounds.containsPoint(pos) // return ( // blockPos.x >= this.bounds.min.x && // blockPos.z >= this.bounds.min.z && @@ -200,5 +203,5 @@ export class PatchesMapBase { /** * Merges all patches as single data container */ - asMergedContainer() {} + asMergedContainer() { } } diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index 1e51724..e505d29 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -42,9 +42,9 @@ const BlockDataBitAllocation = { // for debug use only const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return WorldConf.debug.patchBordersHighlightColor && + return WorldConf.debug.patch.borderHighlightColor && (localPos.x === 1 || localPos.z === 1) - ? WorldConf.debug.patchBordersHighlightColor + ? WorldConf.debug.patch.borderHighlightColor : blockType } @@ -111,9 +111,9 @@ export class GroundPatch extends DataContainer { return local ? new Box2(rangeMin, rangeMax) : new Box2( - asVect2(this.toLocalPos(asVect3(rangeMin))), - asVect2(this.toLocalPos(asVect3(rangeMax))), - ) + this.toLocalPos(rangeMin), + this.toLocalPos(rangeMax), + ) } override getIndex(localPos: Vector2 | Vector3) { @@ -125,34 +125,34 @@ export class GroundPatch extends DataContainer { ) } - getBlock(inputPos: Vector3, isLocalPos = true) { + getBlock(inputPos: Vector2 | Vector3, isLocalPos = false) { + inputPos = inputPos instanceof Vector2 ? inputPos : asVect2(inputPos) const isWithingRange = isLocalPos ? this.inLocalRange(inputPos) - : this.inGlobalRange(inputPos) + : this.inWorldRange(inputPos) let block: PatchBlock | undefined if (isWithingRange) { const localPos = isLocalPos ? inputPos : this.toLocalPos(inputPos) - const pos = isLocalPos ? this.toGlobalPos(inputPos) : inputPos + const pos = isLocalPos ? this.toWorldPos(inputPos) : inputPos const blockIndex = this.getIndex(localPos) const blockData = this.readBlockData(blockIndex) || BlockType.NONE - localPos.y = blockData.level - pos.y = blockData.level block = { index: blockIndex, - pos: this.toGlobalPos(localPos), - localPos, + pos: asVect3(pos, blockData.level), + localPos: asVect3(localPos, blockData.level), data: blockData, } } return block } - setBlock(pos: Vector3, blockData: BlockData, isLocalPos = false) { + setBlock(inputPos: Vector2 | Vector3, blockData: BlockData, isLocalPos = false) { + inputPos = inputPos instanceof Vector2 ? inputPos : asVect2(inputPos) const isWithingPatch = isLocalPos - ? this.inLocalRange(pos) - : this.inGlobalRange(pos) + ? this.inLocalRange(inputPos) + : this.inWorldRange(inputPos) if (isWithingPatch) { - const localPos = isLocalPos ? pos : this.toLocalPos(pos) + const localPos = isLocalPos ? inputPos : this.toLocalPos(inputPos) const blockIndex = this.getIndex(localPos) this.writeBlockData(blockIndex, blockData) } @@ -172,26 +172,25 @@ export class GroundPatch extends DataContainer { ? this.adjustRangeBox(rangeBox) : this.localExtendedBox - const isMarginBlock = ({ x, z }: { x: number; z: number }) => + const isMarginBlock = ({ x, y }: { x: number; y: number }) => !rangeBox && this.margin > 0 && (x === localBbox.min.x || x === localBbox.max.x - 1 || - z === localBbox.min.y || - z === localBbox.max.y - 1) + y === localBbox.min.y || + y === localBbox.max.y - 1) let index = 0 for (let { x } = localBbox.min; x < localBbox.max.x; x++) { for (let { y } = localBbox.min; y < localBbox.max.y; y++) { - const localPos = new Vector3(x, 0, y) + const localPos = new Vector2(x, y) if (!skipMargin || !isMarginBlock(localPos)) { index = rangeBox ? this.getIndex(localPos) : index const blockData = this.readBlockData(index) || BlockType.NONE - localPos.y = blockData.level const block: PatchBlock = { index, - pos: this.toGlobalPos(localPos), - localPos, + pos: asVect3(this.toWorldPos(localPos), blockData.level), + localPos: asVect3(localPos, blockData.level), data: blockData, } yield block diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 66d9167..8f86da6 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -8,7 +8,6 @@ import { GroundPatch, ProcLayer, WorldComputeProxy, - WorldConf, } from '../index' import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' @@ -117,9 +116,9 @@ export class BoardContainer extends GroundPatch { obstacles: EntityData[] holes: EntityData[] } = { - obstacles: [], - holes: [], - } + obstacles: [], + holes: [], + } // swapContainer!: GroundPatch //Uint32Array /** @@ -165,8 +164,6 @@ export class BoardContainer extends GroundPatch { get overridingContainer() { if (!this.output.overridingContainer) { const overridingContainer = this.shapeBoard() - WorldConf.debug.boardSideSplitColoring && - this.boardSplit(overridingContainer) // const boardEntitiesBlocks: Block[] = [] const obstacles: Block[] = this.trimTrees(overridingContainer) const holes: Block[] = this.getHolesAreasBis( @@ -215,18 +212,6 @@ export class BoardContainer extends GroundPatch { return false } - overrideBlockData(block: PatchBlock) { - const blockData = block.data - - blockData.mode = BlockMode.BOARD_CONTAINER - blockData.level = this.input.center.y - blockData.type = this.isWithinBoard(block.pos) - ? blockData.type - : BlockType.DBG_ORANGE - - return blockData - } - *iterBoardBlock() { const blocks = this.iterBlocksQuery(undefined, true) // const blocks = this.iterPatchesBlocks() @@ -254,10 +239,14 @@ export class BoardContainer extends GroundPatch { for (const block of boardBlocks) { // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) if (this.isWithinBoard(block.pos)) { - tempContainer.writeBlockData(block.index, this.overrideBlockData(block)) + block.data.mode = BlockMode.BOARD_CONTAINER + block.data.level = center.y + // override block data + tempContainer.writeBlockData(block.index, block.data) finalBounds.expandByPoint(asVect2(block.pos)) } } + // copy content over final container const bounds = new Box2(asVect2(center), asVect2(center)) bounds.expandByVector(new Vector2(1, 1).multiplyScalar(10)) @@ -285,7 +274,7 @@ export class BoardContainer extends GroundPatch { const entities = spawnLocs .map(loc => { const startPos = asVect3(loc) - const block = boardContainer.getBlock(startPos, false) + const block = boardContainer.getBlock(startPos) return block }) .filter(block => block && this.isWithinBoard(block.pos)) as PatchBlock[] @@ -293,51 +282,6 @@ export class BoardContainer extends GroundPatch { return entities } - // Moved to SDK - // genStartPositions(boardContainer: GroundPatch, otherEntities: PatchBlock[]) { - // const startPositions = this.getBoardEntities(boardContainer, BoardContainer.startPosDistribution) - // WorldConf.debug.boardStartPosHighlightColor && - // startPositions.forEach(block => { - // block.data.type = WorldConf.debug.boardStartPosHighlightColor - // block.data.mode = BlockMode.DEFAULT - // // this.swapContainer.writeBlockData(block.index, block.data) - // boardContainer.setBlock(block.pos, block.data) - // }) - // return startPositions - // } - - boardSplit(boardContainer: GroundPatch) { - const { center } = this.input - const boardBlocks = boardContainer.iterBlocksQuery(undefined, false) - const dims = boardContainer.bounds.getSize(new Vector2()) - const ratio = dims.y / dims.x - const projectionAxis = ratio > 1 ? new Vector3(dims.x, -dims.y) : dims // ratio > 1 ? new Vector2(0, 1) : new Vector2(1, 0) //dims.clone().normalize() //ratio > 1 ? new Vector2(0, 1) : new Vector2(1, 0) - const check = (pos: Vector3) => { - const diff = asVect2(pos.clone().sub(center)) - // const projection = pos2.dot(projectionAxis)X - const projection = diff.dot(projectionAxis) - return projection < 0 - // return ratio < 1 ? pos.z < center.z : pos.x < center.x - // if (Math.abs(ratio - 1) < 0.15){ - // console.log(`board dim ratio: ${ratio}`) - // return projection < 0 - // } - // else return ratio < 1 ? pos.z < center.z : pos.x < center.x - } - for (const block of boardBlocks) { - if (this.isWithinBoard(block.pos)) { - block.data.type = check(block.pos) - ? BlockType.DBG_ORANGE - : BlockType.DBG_GREEN - boardContainer.writeBlockData(block.index, block.data) - } - // else { - // block.data.type = BlockType.DBG_LIGHT - // boardContainer.writeBlockData(block.index, block.data) - // } - } - } - isGroundHole(testPos: Vector3) { return BoardContainer.holesMapDistribution.eval(testPos) < 0.15 } @@ -403,7 +347,7 @@ export class BoardContainer extends GroundPatch { const trunks = this.entities.obstacles .map(entity => { const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = boardContainer.getBlock(entityCenter, false) + const entityCenterBlock = boardContainer.getBlock(entityCenter) entityCenter.y = entity.bbox.min.y return entityCenterBlock }) diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index 888160f..a61314e 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -14,8 +14,12 @@ export class WorldConf { static defaultDistMapPeriod = 4 * WorldConf.patchSize static debug = { - patchBordersHighlightColor: BlockType.NONE, - boardStartPosHighlightColor: BlockType.NONE, - boardSideSplitColoring: false, + patch: { + borderHighlightColor: BlockType.NONE + }, + board: { + startPosHighlightColor: BlockType.NONE, + splitSidesColoring: false + } } } diff --git a/src/procgen/BlueNoisePattern.ts b/src/procgen/BlueNoisePattern.ts index 135de7d..e1b0a0c 100644 --- a/src/procgen/BlueNoisePattern.ts +++ b/src/procgen/BlueNoisePattern.ts @@ -69,7 +69,7 @@ export class BlueNoisePattern { return pos.clone().sub(this.getPatchOrigin(patchId)) } - toPatchGlobalPos(relativePos: Vector2, patchId: Vector2) { + toPatchWorldPos(relativePos: Vector2, patchId: Vector2) { return relativePos.clone().add(this.getPatchOrigin(patchId)) } @@ -77,7 +77,7 @@ export class BlueNoisePattern { *iterPatchElements(patchOffset: Vector2) { // relative to global pos conv for (const relativePos of this.elements) { - const pos = this.toPatchGlobalPos(relativePos, patchOffset) + const pos = this.toPatchWorldPos(relativePos, patchOffset) yield pos } } From b44b358f05b0489aa3aaf379a541a7e20310c45b Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 10 Sep 2024 14:47:53 +0000 Subject: [PATCH 44/45] fix: types, formatting --- src/common/utils.ts | 32 +++++++++++++++--------- src/datacontainers/DataContainers.ts | 6 ++--- src/datacontainers/GroundPatch.ts | 11 +++++---- src/feats/BoardContainer.ts | 37 +++++++++++++++++++++++----- src/misc/WorldConfig.ts | 6 ++--- 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/common/utils.ts b/src/common/utils.ts index 756854b..f306620 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -72,7 +72,7 @@ const directNeighbours2D = [ Adjacent2dPos.left, Adjacent2dPos.right, Adjacent2dPos.top, - Adjacent2dPos.bottom + Adjacent2dPos.bottom, ] /** @@ -176,16 +176,24 @@ const getAdjacent3dCoords = (pos: Vector3, dir: Adjacent3dPos): Vector3 => { } } -const getNeighbours2D = (pos: Vector2, directNeighboursOnly = false): Vector2[] => { - const neighbours = directNeighboursOnly? directNeighbours2D : Object.values(Adjacent2dPos) - .filter(v => !isNaN(Number(v))) +const getNeighbours2D = ( + pos: Vector2, + directNeighboursOnly = false, +): Vector2[] => { + const neighbours = directNeighboursOnly + ? directNeighbours2D + : Object.values(Adjacent2dPos).filter(v => !isNaN(Number(v))) return neighbours.map(type => getAdjacent2dCoords(pos, type as number)) } -const getNeighbours3D = (pos: Vector3, directNeighboursOnly = false): Vector3[] => { - const neighbours = directNeighboursOnly ? directNeighbours3D : Object.values(Adjacent3dPos) - .filter(v => !isNaN(Number(v))) - return neighbours.map(type => getAdjacent3dCoords(pos, type as number)) +const getNeighbours3D = ( + pos: Vector3, + directNeighboursOnly = false, +): Vector3[] => { + const neighbours = directNeighboursOnly + ? directNeighbours3D + : Object.values(Adjacent3dPos).filter(v => !isNaN(Number(v))) + return neighbours.map(type => getAdjacent3dCoords(pos, type as number)) } const getPatchPoints = (patchBBox: Box3, clearY = true) => { @@ -284,10 +292,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/DataContainers.ts b/src/datacontainers/DataContainers.ts index c3140a9..1ee7935 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -1,9 +1,7 @@ -import { Vector2, Box2, Vector3 } from 'three' +import { Vector2, Box2 } from 'three' import { PatchKey } from '../common/types' import { - asVect2, - asVect3, getPatchId, parsePatchKey, patchBoxFromKey, @@ -203,5 +201,5 @@ export class PatchesMapBase { /** * Merges all patches as single data container */ - asMergedContainer() { } + asMergedContainer() {} } diff --git a/src/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index e505d29..182cc47 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -110,10 +110,7 @@ export class GroundPatch extends DataContainer { ) return local ? new Box2(rangeMin, rangeMax) - : new Box2( - this.toLocalPos(rangeMin), - this.toLocalPos(rangeMax), - ) + : new Box2(this.toLocalPos(rangeMin), this.toLocalPos(rangeMax)) } override getIndex(localPos: Vector2 | Vector3) { @@ -146,7 +143,11 @@ export class GroundPatch extends DataContainer { return block } - setBlock(inputPos: Vector2 | Vector3, blockData: BlockData, isLocalPos = false) { + setBlock( + inputPos: Vector2 | Vector3, + blockData: BlockData, + isLocalPos = false, + ) { inputPos = inputPos instanceof Vector2 ? inputPos : asVect2(inputPos) const isWithingPatch = isLocalPos ? this.inLocalRange(inputPos) diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 8f86da6..95fa895 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -4,10 +4,12 @@ import { Block, EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, + BoardUtils, DataContainer, GroundPatch, ProcLayer, WorldComputeProxy, + WorldConf, } from '../index' import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' @@ -116,9 +118,9 @@ export class BoardContainer extends GroundPatch { obstacles: EntityData[] holes: EntityData[] } = { - obstacles: [], - holes: [], - } + obstacles: [], + holes: [], + } // swapContainer!: GroundPatch //Uint32Array /** @@ -210,7 +212,7 @@ export class BoardContainer extends GroundPatch { } } return false - } + }; *iterBoardBlock() { const blocks = this.iterBlocksQuery(undefined, true) @@ -394,7 +396,30 @@ export class BoardContainer extends GroundPatch { this.output.data.push(boardBlock) } const { origin, size, data } = this.output - const boardOutputData: BoardOutputData = { origin, size, data } - return boardOutputData + const board: BoardOutputData = { origin, size, data } + if (WorldConf.debug.board.splitSidesColoring) { + const boardSides = BoardUtils.splitBoard(board) + // boardSides.first.forEach(blockPos => { + // const block = this.getBlock(blockPos) + // if (block) { + // block.data.type = BlockType.DBG_ORANGE + // this.setBlock(block.pos, block.data) + // } + // }) + let index = 0 + board.data.forEach(boardBlock => { + if (boardBlock.type) { + const blockData = this.overridingContainer.readBlockData(index) + const blockPos = this.overridingContainer.getLocalPosFromIndex(index) + if (blockData) { + blockData.type = BlockType.DBG_ORANGE + this.setBlock(blockPos, blockData, true) + } + } + index++ + }) + console.log(boardSides) + } + return board } } diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index a61314e..d7ee1e1 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -15,11 +15,11 @@ export class WorldConf { static defaultDistMapPeriod = 4 * WorldConf.patchSize static debug = { patch: { - borderHighlightColor: BlockType.NONE + borderHighlightColor: BlockType.NONE, }, board: { startPosHighlightColor: BlockType.NONE, - splitSidesColoring: false - } + splitSidesColoring: false, + }, } } From 3a0886bfd3e823904008bddc8db22aa42c6b519c Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 10 Sep 2024 14:59:00 +0000 Subject: [PATCH 45/45] fix: accidental commit roll back + fix wrong type --- src/datacontainers/GroundPatchesMap.ts | 3 +-- src/datacontainers/PatchesMap.ts | 4 ++-- src/feats/BoardContainer.ts | 29 ++------------------------ 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts index 5e34c01..6d6f297 100644 --- a/src/datacontainers/GroundPatchesMap.ts +++ b/src/datacontainers/GroundPatchesMap.ts @@ -1,7 +1,6 @@ import { Box2, Vector2, Vector3 } from 'three' import { PatchKey } from '../common/types' -import { asVect3 } from '../common/utils' import { GroundPatch, WorldComputeProxy, WorldConf } from '../index' import { PatchesMap } from './PatchesMap' @@ -103,7 +102,7 @@ export class CacheContainer extends PatchesMap { maxXmaxZ, ] const patchNeighbours: GroundPatch[] = neighboursCenters - .map(patchCenter => this.findPatch(asVect3(patchCenter))) + .map(patchCenter => this.findPatch(patchCenter)) .filter(patch => patch) as GroundPatch[] return patchNeighbours } diff --git a/src/datacontainers/PatchesMap.ts b/src/datacontainers/PatchesMap.ts index 0b61f14..92181e7 100644 --- a/src/datacontainers/PatchesMap.ts +++ b/src/datacontainers/PatchesMap.ts @@ -1,4 +1,4 @@ -import { Box2, Vector3 } from 'three' +import { Box2, Vector2 } from 'three' import { PatchKey } from '../common/types' @@ -85,7 +85,7 @@ export class PatchesMap> extends PatchesMapBase { return patchKeysDiff } - findPatch(blockPos: Vector3) { + findPatch(blockPos: Vector2) { const res = this.availablePatches.find(patch => patch.containsPoint(blockPos), ) diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 95fa895..5021df4 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -4,12 +4,10 @@ import { Block, EntityData, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, - BoardUtils, DataContainer, GroundPatch, ProcLayer, WorldComputeProxy, - WorldConf, } from '../index' import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' @@ -396,30 +394,7 @@ export class BoardContainer extends GroundPatch { this.output.data.push(boardBlock) } const { origin, size, data } = this.output - const board: BoardOutputData = { origin, size, data } - if (WorldConf.debug.board.splitSidesColoring) { - const boardSides = BoardUtils.splitBoard(board) - // boardSides.first.forEach(blockPos => { - // const block = this.getBlock(blockPos) - // if (block) { - // block.data.type = BlockType.DBG_ORANGE - // this.setBlock(block.pos, block.data) - // } - // }) - let index = 0 - board.data.forEach(boardBlock => { - if (boardBlock.type) { - const blockData = this.overridingContainer.readBlockData(index) - const blockPos = this.overridingContainer.getLocalPosFromIndex(index) - if (blockData) { - blockData.type = BlockType.DBG_ORANGE - this.setBlock(blockPos, blockData, true) - } - } - index++ - }) - console.log(boardSides) - } - return board + const boardOutputData: BoardOutputData = { origin, size, data } + return boardOutputData } }