diff --git a/src/common/utils.ts b/src/common/utils.ts index f306620..fb2da56 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,7 +1,5 @@ import { Box2, Box3, Vector2, Vector2Like, Vector3, Vector3Like } from 'three' -import { WorldConf } from '../index' - import { Adjacent2dPos, Adjacent3dPos, @@ -339,6 +337,34 @@ const patchBoxFromKey = (patchKey: string, patchDims: Vector2) => { return bbox } +const getPatchRange = (bounds: Box2, patchDims: Vector2) => { + const rangeMin = getPatchId(bounds.min, patchDims) + const rangeMax = patchUpperId(bounds.max, patchDims) // .addScalar(1) + const patchRange = new Box2(rangeMin, rangeMax) + return patchRange +} + +const getPatchIds = (bounds: Box2, patchDims: Vector2) => { + const patchIds = [] + const patchRange = getPatchRange(bounds, patchDims) + // iter elements on computed range + const { min, max } = patchRange + for (let { y } = min; y <= max.y; y++) { + for (let { x } = min; x <= max.x; x++) { + patchIds.push(new Vector2(x, y)) + } + } + return patchIds +} + +const getRoundedBox = (bounds: Box2, patchDims: Vector2) => { + const { min, max } = getPatchRange(bounds, patchDims) + min.multiply(patchDims) + max.multiply(patchDims) + const extBbox = new Box2(min, max) + return extBbox +} + const parseChunkKey = (chunkKey: ChunkKey) => { const chunkId = new Vector3( parseInt(chunkKey.split('_')[1] as string), @@ -361,10 +387,7 @@ function genChunkIds(patchId: PatchId, ymin: number, ymax: number) { return chunk_ids } -const chunkBoxFromId = ( - chunkId: ChunkId, - patchSize: number = WorldConf.patchSize, -) => { +const chunkBoxFromId = (chunkId: ChunkId, patchSize: number) => { const bmin = chunkId.clone().multiplyScalar(patchSize) const bmax = chunkId.clone().addScalar(1).multiplyScalar(patchSize) const chunkBbox = new Box3(bmin, bmax) @@ -391,6 +414,9 @@ export { getPatchId, patchUpperId, serializePatchId, + getPatchRange, + getPatchIds, + getRoundedBox, patchBoxFromKey, parseChunkKey, serializeChunkId, diff --git a/src/datacontainers/DataContainers.ts b/src/datacontainers/DataContainers.ts index 1ee7935..4971698 100644 --- a/src/datacontainers/DataContainers.ts +++ b/src/datacontainers/DataContainers.ts @@ -2,17 +2,12 @@ import { Vector2, Box2 } from 'three' import { PatchKey } from '../common/types' import { - getPatchId, parsePatchKey, patchBoxFromKey, - patchUpperId, serializePatchId, } from '../common/utils' import { WorldConf } from '../index' -const getDefaultPatchDim = () => - new Vector2(WorldConf.patchSize, WorldConf.patchSize) - /** * Multi purpose low level data container */ @@ -29,7 +24,7 @@ export abstract class DataContainer { const bounds = boundsOrPatchKey instanceof Box2 ? boundsOrPatchKey.clone() - : patchBoxFromKey(boundsOrPatchKey, getDefaultPatchDim()) + : patchBoxFromKey(boundsOrPatchKey, WorldConf.regularPatchDimensions) this.bounds = bounds this.dimensions = bounds.getSize(new Vector2()) this.margin = margin @@ -87,14 +82,14 @@ export abstract class DataContainer { 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++) { + for (let { y } = overlap.min; y < overlap.max.y; y++) { // const globalStartPos = new Vector3(x, 0, overlap.min.y) - const globalStartPos = new Vector2(x, overlap.min.y) + const globalStartPos = new Vector2(overlap.min.x, y) const targetLocalStartPos = target.toLocalPos(globalStartPos) const sourceLocalStartPos = source.toLocalPos(globalStartPos) let targetIndex = target.getIndex(targetLocalStartPos) let sourceIndex = source.getIndex(sourceLocalStartPos) - for (let { y } = overlap.min; y < overlap.max.y; y++) { + for (let { x } = overlap.min; x < overlap.max.x; x++) { const sourceVal = source.rawData[sourceIndex] if (sourceVal) { target.rawData[targetIndex] = sourceVal @@ -125,12 +120,12 @@ export abstract class DataContainer { } getIndex(localPos: Vector2) { - return localPos.x * this.dimensions.y + localPos.y + return localPos.y * this.dimensions.x + localPos.x } getLocalPosFromIndex(index: number) { - const y = index % this.dimensions.y - const x = Math.floor(index / this.dimensions.y) + const y = Math.floor(index / this.dimensions.y) + const x = index % this.dimensions.x return new Vector2(x, y) } @@ -147,59 +142,25 @@ export abstract class DataContainer { return origin.add(pos) } + isOverlapping(bounds: Box2) { + const nonOverlapping = + this.bounds.max.x <= bounds.min.x || + this.bounds.min.x >= bounds.max.x || + this.bounds.max.y <= bounds.min.y || + this.bounds.min.y >= bounds.max.y + return !nonOverlapping + } + containsPoint(pos: Vector2) { - return this.bounds.containsPoint(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 - // ) + // return this.bounds.containsPoint(pos) + return ( + pos.x >= this.bounds.min.x && + pos.y >= this.bounds.min.y && + pos.x < this.bounds.max.x && + pos.y < this.bounds.max.y + ) } // abstract get chunkIds(): ChunkId[] // abstract toChunks(): any } - -/** - * PatchesMap base class - */ -export class PatchesMapBase { - patchDimensions: Vector2 - constructor(patchDim: Vector2) { - this.patchDimensions = patchDim - } - - 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(bounds: Box2) { - const patchIds = [] - const patchRange = this.getPatchRange(bounds) - // 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/GroundMap.ts b/src/datacontainers/GroundMap.ts new file mode 100644 index 0000000..59464d8 --- /dev/null +++ b/src/datacontainers/GroundMap.ts @@ -0,0 +1,279 @@ +import { Box2, Vector2 } from 'three' + +import { PatchKey } from '../common/types' +import { getPatchIds, serializePatchId } from '../common/utils' +import { GroundPatch, WorldConf } from '../index' + +export class PatchesContainer { + patchLookup: Record = {} + + get keys() { + return Object.keys(this.patchLookup) + } + + get patches() { + return Object.values(this.patchLookup) + } + + get empty() { + const emptyPatches = this.patches.filter(patch => patch.isEmpty) + return emptyPatches + } + + getOverlappingPatches(inputBounds: Box2) { + return this.patches.filter(patch => patch.isOverlapping(inputBounds)) + } + + findPatch(blockPos: Vector2) { + const res = this.patches.find(patch => patch.containsPoint(blockPos)) + return res + } +} + +export class GroundMap extends PatchesContainer { + bounds: Box2 = new Box2() + patchDimensions + + constructor() { + super() + this.patchDimensions = WorldConf.regularPatchDimensions + } + + // adjustBounds(bounds: Box2) { + // this.bounds = bounds + // // rebuild patch index + // this.rebuildPatchIndex(bounds) + // this.loadEmpty() + // } + + rebuildPatchIndex(bounds: Box2) { + this.bounds = bounds + const patchKeys = getPatchIds(bounds, this.patchDimensions).map(patchId => + serializePatchId(patchId), + ) + const foundOrMissing = patchKeys.map(key => this.patchLookup[key] || key) + const changesCount = foundOrMissing.filter( + item => typeof item === 'string', + ).length + if (changesCount > 0) { + const patchLookup: Record = {} + foundOrMissing + .map(item => + item instanceof GroundPatch ? item : new GroundPatch(item), + ) + .forEach(patch => (patchLookup[patch.key] = patch)) + this.patchLookup = patchLookup + } + return changesCount + } + + async loadEmpty(on_the_fly = true) { + // const patchRequests = WorldComputeProxy.instance.iterPatchCompute(batch) + if (on_the_fly) { + // progressive loading + const patches = this.empty + async function* asyncGenerator() { + for (const patch of patches) { + await patch.fillGroundData() + yield patch + } + } + return asyncGenerator() + } else { + // all at once + return await Promise.all( + this.empty.map(async patch => { + await patch.fillGroundData() + return patch + }), + ) + } + } + + // getBlock(blockPos: Vector3) { + // return this.findPatch(blockPos)?.getBlock(blockPos, false) + // } + + // 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 + // } + + // *iterAllPatchesBlocks() { + // for (const patch of this.availablePatches) { + // const blocks = patch.iterOverBlocks(undefined, false, false) + // for (const block of blocks) { + // yield block + // } + // } + // } +} + +// get count() { +// return Object.keys(this.patchLookup).length +// } + +// get patchKeys() { +// return Object.keys(this.patchLookup) +// } + +// get availablePatches() { +// return Object.values(this.patchLookup).filter(val => val) as T[] +// } + +// 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 GroundPatch(key)) +// this.availablePatches.forEach(patch=>patch.iterOverBlocks) +// } + +// 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 // (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) { +// 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 +// } + +// 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 +// } + +// 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/datacontainers/GroundPatch.ts b/src/datacontainers/GroundPatch.ts index 182cc47..eda37b5 100644 --- a/src/datacontainers/GroundPatch.ts +++ b/src/datacontainers/GroundPatch.ts @@ -6,14 +6,11 @@ import { parseThreeStub, asVect3, asVect2, - asBox2, } from '../common/utils' -import { WorldComputeProxy, WorldConf } from '../index' +import { WorldComputeProxy } from '../index' import { BlockType } from '../procgen/Biome' import { DataContainer } from './DataContainers' -import { EntityChunk } from './EntityChunk' -import { WorldChunk } from './WorldChunk' export enum BlockMode { DEFAULT, @@ -40,18 +37,11 @@ const BlockDataBitAllocation = { mode: 3, // support for 8 different block mode } -// for debug use only -const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { - return WorldConf.debug.patch.borderHighlightColor && - (localPos.x === 1 || localPos.z === 1) - ? WorldConf.debug.patch.borderHighlightColor - : blockType -} - export type BlockIteratorRes = IteratorResult export class GroundPatch extends DataContainer { rawData: Uint32Array + isEmpty = true constructor(boundsOrPatchKey: Box2 | PatchKey = new Box2(), margin = 1) { super(boundsOrPatchKey, margin) @@ -63,6 +53,12 @@ export class GroundPatch extends DataContainer { this.rawData = new Uint32Array(this.extendedDims.x * this.extendedDims.y) } + duplicate() { + const copy = new GroundPatch(this.key || this.bounds, this.margin) + copy.rawData.set(this.rawData) + return copy + } + decodeBlockData(rawData: number): BlockData { const shift = BlockDataBitAllocation const level = @@ -96,9 +92,8 @@ export class GroundPatch extends DataContainer { this.rawData[blockIndex] = this.encodeBlockData(blockData) } - adjustRangeBox(rangeBox: Box2 | Vector2, local = false) { - rangeBox = - rangeBox instanceof Box2 ? rangeBox : new Box2(rangeBox, rangeBox) + adjustInputBounds(input: Box2 | Vector2, local = false) { + const rangeBox = input instanceof Box2 ? input : new Box2(input, input) const { min, max } = local ? this.localBox : this.bounds const rangeMin = new Vector2( Math.max(Math.floor(rangeBox.min.x), min.x), @@ -116,8 +111,8 @@ export class GroundPatch extends DataContainer { override getIndex(localPos: Vector2 | Vector3) { localPos = localPos instanceof Vector2 ? localPos : asVect2(localPos) return ( - (localPos.x + this.margin) * this.extendedDims.y + - localPos.y + + (localPos.y + this.margin) * this.extendedDims.x + + localPos.x + this.margin ) } @@ -149,10 +144,10 @@ export class GroundPatch extends DataContainer { isLocalPos = false, ) { inputPos = inputPos instanceof Vector2 ? inputPos : asVect2(inputPos) - const isWithingPatch = isLocalPos + const isWithinPatch = isLocalPos ? this.inLocalRange(inputPos) : this.inWorldRange(inputPos) - if (isWithingPatch) { + if (isWithinPatch) { const localPos = isLocalPos ? inputPos : this.toLocalPos(inputPos) const blockIndex = this.getIndex(localPos) this.writeBlockData(blockIndex, blockData) @@ -167,26 +162,26 @@ export class GroundPatch extends DataContainer { * @param rangeBox iteration range as global coords * @param skipMargin */ - *iterBlocksQuery(rangeBox?: Box2 | Vector2, skipMargin = true) { + *iterBlocksQuery(iterBounds?: Box2 | Vector2, skipMargin = true) { // convert to local coords to speed up iteration - const localBbox = rangeBox - ? this.adjustRangeBox(rangeBox) + const localBounds = iterBounds + ? this.adjustInputBounds(iterBounds) : this.localExtendedBox const isMarginBlock = ({ x, y }: { x: number; y: number }) => - !rangeBox && + !iterBounds && this.margin > 0 && - (x === localBbox.min.x || - x === localBbox.max.x - 1 || - y === localBbox.min.y || - y === localBbox.max.y - 1) + (x === localBounds.min.x || + x === localBounds.max.x - 1 || + y === localBounds.min.y || + y === localBounds.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++) { + for (let { y } = localBounds.min; y < localBounds.max.y; y++) { + for (let { x } = localBounds.min; x < localBounds.max.x; x++) { const localPos = new Vector2(x, y) if (!skipMargin || !isMarginBlock(localPos)) { - index = rangeBox ? this.getIndex(localPos) : index + index = iterBounds ? this.getIndex(localPos) : index const blockData = this.readBlockData(index) || BlockType.NONE const block: PatchBlock = { index, @@ -225,46 +220,7 @@ export class GroundPatch extends DataContainer { 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) - } + this.isEmpty = false } // getBlocksRow(zRowIndex: number) { diff --git a/src/datacontainers/GroundPatchesMap.ts b/src/datacontainers/GroundPatchesMap.ts deleted file mode 100644 index 6d6f297..0000000 --- a/src/datacontainers/GroundPatchesMap.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Box2, Vector2, Vector3 } from 'three' - -import { PatchKey } from '../common/types' -import { GroundPatch, WorldComputeProxy, WorldConf } from '../index' - -import { PatchesMap } from './PatchesMap' - -const getDefaultPatchDim = () => - new Vector2(WorldConf.patchSize, WorldConf.patchSize) - -/** - * Blocks cache - */ -export class CacheContainer extends PatchesMap { - static cachePowRadius = 2 - static cacheSize = WorldConf.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 - } - - async populate(batch: PatchKey[]) { - this.pendingRefresh = true - const batchIter = WorldComputeProxy.instance.iterPatchCompute(batch) - // populate cache without blocking execution - for await (const patch of batchIter) { - if (patch.key) { - this.patchLookup[patch.key] = patch - this.bbox.union(patch.bounds) - } - } - this.pendingRefresh = false - } - - /** - * - * @param center - * @param dryRun - * @returns true if cache was update, false otherwise - */ - async refresh(bbox: Box2) { - //, patchMask = () => true) { - let changesDiff - if (!this.pendingRefresh) { - const emptyContainer = new PatchesMap(this.patchDimensions) - emptyContainer.init(bbox) - changesDiff = emptyContainer.compareWith(CacheContainer.instance) - const hasChanged = Object.keys(changesDiff).length > 0 - - // (!cacheCenter.equals(this.cacheCenter) || cachePatchCount === 0) - 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) - this.builtInCache && (await this.populate(this.missingPatchKeys)) - } - } - // return patch keys changes - return changesDiff - } - - 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 => - overlappingBounds(patch.bounds, inputBounds), - ) - } - - getNearPatches(patch: GroundPatch) { - const dim = patch.dimensions - 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, - minZ, - maxZ, - minXminZ, - minXmaxZ, - maxXminZ, - maxXmaxZ, - ] - const patchNeighbours: GroundPatch[] = neighboursCenters - .map(patchCenter => this.findPatch(patchCenter)) - .filter(patch => patch) as GroundPatch[] - 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/datacontainers/PatchesMap.ts b/src/datacontainers/PatchesMap.ts deleted file mode 100644 index 92181e7..0000000 --- a/src/datacontainers/PatchesMap.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { Box2, Vector2 } from 'three' - -import { PatchKey } from '../common/types' - -import { DataContainer, PatchesMapBase } from './DataContainers' - -/** - * Finite map made from patch aggregation - */ -export class PatchesMap> extends PatchesMapBase { - bbox: Box2 = new Box2() - patchLookup: Record = {} - - init( - bbox: Box2, - // patchBboxFilter = (patchBbox: Box3) => patchBbox, - ) { - this.bbox = bbox - this.patchLookup = {} - 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}` - // if (patchBboxFilter(patchBox)) { - this.patchLookup[patchKey] = null - // } - } - } - } - - override getPatchRange() { - return super.getPatchRange(this.bbox) - } - - override getRoundedBox() { - return super.getRoundedBox(this.bbox) - } - - get count() { - return Object.keys(this.patchLookup).length - } - - get patchKeys() { - return Object.keys(this.patchLookup) - } - - get availablePatches() { - return Object.values(this.patchLookup).filter(val => val) as T[] - } - - 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 GroundPatch(key)) - // this.availablePatches.forEach(patch=>patch.iterOverBlocks) - // } - - 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 // (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) { - 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 - } - - findPatch(blockPos: Vector2) { - const res = this.availablePatches.find(patch => - patch.containsPoint(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/datacontainers/RandomDistributionMap.ts b/src/datacontainers/RandomDistributionMap.ts index 4c24d9f..8a4c412 100644 --- a/src/datacontainers/RandomDistributionMap.ts +++ b/src/datacontainers/RandomDistributionMap.ts @@ -5,8 +5,8 @@ import { ProcLayer } from '../procgen/ProcLayer' import { BlueNoisePattern } from '../procgen/BlueNoisePattern' import { EntityData } from '../common/types' import { WorldConf } from '../index' +import { getPatchIds } from '../common/utils' -import { PatchesMapBase } from './DataContainers' // import { Adjacent2dPos } from '../common/types' // import { getAdjacent2dCoords } from '../common/utils' @@ -25,21 +25,20 @@ 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 + * Approximated random distribution using infinite map made from patch repetition + * independant and deterministic */ -export class PseudoDistributionMap extends PatchesMapBase { +export class PseudoDistributionMap { + patchDimensions: Vector2 repeatedPattern: BlueNoisePattern densityMap: ProcLayer constructor( - bbox: Box2 = distMapDefaultBox, + bounds: Box2 = distMapDefaultBox, distParams: any = distMapDefaults, ) { - super(bbox.getSize(new Vector2())) - this.repeatedPattern = new BlueNoisePattern(bbox, distParams) + this.patchDimensions = bounds.getSize(new Vector2()) + this.repeatedPattern = new BlueNoisePattern(bounds, distParams) this.densityMap = new ProcLayer(distParams.aleaSeed || '') } @@ -65,11 +64,11 @@ export class PseudoDistributionMap extends PatchesMapBase { } /** - * + * querying/iterating randomly distributed items at block level or from custom bounds * @param entityShaper - * @param inputPointOrArea either test point or range box + * @param inputPointOrArea either test point or bounds * @param spawnProbabilityOverride - * @returns all locations from which entity contains input point or overlaps with range box + * @returns all entities locations overlapping with input point or bounds */ querySpawnLocations( testRange: Vector2 | Box2, @@ -87,7 +86,7 @@ export class PseudoDistributionMap extends PatchesMapBase { // .filter(entityPos => overlapsTest(localTestBox, entityPos)) // .map(relativePos => relativePos.clone().add(offset)) const overlappingEntities: Vector2[] = [] - const patchIds = this.getPatchIds(testBox) + const patchIds = getPatchIds(testBox, this.patchDimensions) for (const patchId of patchIds) { const offset = patchId.clone().multiply(this.patchDimensions) const localTestBox = testBox.clone().translate(offset.clone().negate()) diff --git a/src/feats/BoardContainer.ts b/src/feats/BoardContainer.ts index 5021df4..5ca99dc 100644 --- a/src/feats/BoardContainer.ts +++ b/src/feats/BoardContainer.ts @@ -1,6 +1,6 @@ import { Box2, Vector2, Vector3, Vector3Like } from 'three' -import { Block, EntityData, PatchBlock } from '../common/types' +import { Block, PatchBlock } from '../common/types' import { asVect2, asVect3 } from '../common/utils' import { BlockType, @@ -11,7 +11,7 @@ import { } from '../index' import { PseudoDistributionMap } from '../datacontainers/RandomDistributionMap' import { findBoundingBox } from '../common/math' -import { BlockMode, PatchStub } from '../datacontainers/GroundPatch' +import { BlockData, BlockMode } from '../datacontainers/GroundPatch' export enum BlockCategory { FLAT = 0, @@ -49,23 +49,7 @@ const blockTypeCategoryMapper = (blockType: BlockType) => { } } -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 +// Board hole entities distribution conf const holesDistParams = { aleaSeed: 'boardHoles', minDistance: 10, @@ -79,121 +63,37 @@ const holesDistParams = { * - 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 data output - output: BoardOutputData & { overridingContainer: GroundPatch | undefined } = { - origin: new Vector3(), - size: new Vector2(), - data: [], - overridingContainer: undefined, - } - - entities: { - obstacles: EntityData[] - holes: EntityData[] - } = { - obstacles: [], - holes: [], - } - // swapContainer!: GroundPatch //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 + center + radius + thickness + constructor(boardCenter = new Vector3(), boardParams?: BoardInputParams) { + super() + this.center = boardCenter.clone().floor() || new Vector3() + this.radius = boardParams?.radius || 0 + this.thickness = boardParams?.thickness || 0 BoardContainer.holesMapDistribution.sampling.periodicity = 0.25 } - get overridingContainer() { - if (!this.output.overridingContainer) { - const overridingContainer = this.shapeBoard() - // 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) + async make() { + await this.fillAndShapeBoard() + const obstacles: Block[] = await this.retrieveAndTrimTrees() + const holes: Block[] = this.getHolesAreasBis(obstacles) + holes.forEach(block => this.digGroundHole(block)) } isWithinBoard(blockPos: Vector3) { let isInsideBoard = false - const { thickness, radius, center } = this.input + const { thickness, radius, center } = this if (blockPos) { const heightDiff = Math.abs(blockPos.y - center.y) const dist = asVect2(blockPos).distanceTo(asVect2(center)) @@ -210,17 +110,6 @@ export class BoardContainer extends GroundPatch { } } return false - }; - - *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 - } - } } /** @@ -228,15 +117,20 @@ export class BoardContainer extends GroundPatch { * and adjust final board bounds * @returns */ - shapeBoard() { - const { center } = this.input + async fillAndShapeBoard() { + const { center, radius } = this + const defaultBounds = new Box2().setFromCenterAndSize( + asVect2(center), + new Vector2(radius, radius).multiplyScalar(2), + ) + const tempContainer = new GroundPatch(defaultBounds) + await tempContainer.fillGroundData() // const { ymin, ymax } = this.getMinMax() // const avg = Math.round(ymin + (ymax - ymin) / 2) - const tempContainer = new GroundPatch(this.bounds) const finalBounds = new Box2(asVect2(center), asVect2(center)) - const boardBlocks = this.iterBlocksQuery(undefined, false) + const blocks = tempContainer.iterBlocksQuery(undefined, false) // const boardBlocks = this.iterBoardBlock() - for (const block of boardBlocks) { + for (const block of blocks) { // tempContainer.setBlock(boardBlock.pos, boardBlock.data, false) if (this.isWithinBoard(block.pos)) { block.data.mode = BlockMode.BOARD_CONTAINER @@ -246,16 +140,31 @@ export class BoardContainer extends GroundPatch { finalBounds.expandByPoint(asVect2(block.pos)) } } + // copy content over board container + this.init(finalBounds) + DataContainer.copySourceOverTargetContainer(tempContainer, this) + } - // 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 + async retrieveAndTrimTrees() { + const trees = await WorldComputeProxy.instance.queryEntities(this.bounds) + const trunks = trees + .map(entity => { + const entityCenter = entity.bbox.getCenter(new Vector3()) + const entityCenterBlock = this.getBlock(entityCenter) + entityCenter.y = entity.bbox.min.y + return entityCenterBlock + }) + .filter( + trunkBlock => trunkBlock && this.isWithinBoard(trunkBlock.pos), + ) as PatchBlock[] + + trunks.forEach(trunkBlock => { + trunkBlock.data.type = BlockType.TREE_TRUNK + trunkBlock.data.mode = BlockMode.DEFAULT + trunkBlock.data.level += 1 + this.setBlock(trunkBlock.pos, trunkBlock.data) + }) + return trunks.map(({ pos, data }) => ({ pos, data }) as Block) } // perform local query @@ -286,11 +195,11 @@ export class BoardContainer extends GroundPatch { return BoardContainer.holesMapDistribution.eval(testPos) < 0.15 } - digGroundHole(holeBlock: Block, boardContainer: GroundPatch) { + digGroundHole(holeBlock: Block) { 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) + this.setBlock(holeBlock.pos, holeBlock.data) } getHolesMonoBlocks(boardContainer: GroundPatch) { @@ -324,11 +233,11 @@ export class BoardContainer extends GroundPatch { return holesMulti.map(({ pos, data }) => ({ pos, data }) as Block) } - getHolesAreasBis(boardContainer: GroundPatch, forbiddenBlocks: Block[]) { + getHolesAreasBis(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 blocks = this.iterBlocksQuery() const holes: Block[] = [] for (const block of blocks) { const testPos = block.pos @@ -343,58 +252,57 @@ export class BoardContainer extends GroundPatch { return holes.map(({ pos, data }) => ({ pos, data }) as Block) } - trimTrees(boardContainer: GroundPatch) { - const trunks = this.entities.obstacles - .map(entity => { - const entityCenter = entity.bbox.getCenter(new Vector3()) - const entityCenterBlock = boardContainer.getBlock(entityCenter) - 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) - } - - override fromStub(boardStub: BoardStub) { - super.fromStub(boardStub) - const { input, output } = boardStub - this.input = input - this.output = { ...output, overridingContainer: undefined } - return this - } - - override toStub(): BoardStub { - const { input, output } = this - const boardStub: BoardStub = { - ...super.toStub(), - input, - output, - } - return boardStub - } - + /** + * Convert board ground container to exported format + */ exportBoardData() { - const boardBlocks = this.overridingContainer.iterBlocksQuery() + const origin = asVect3(this.bounds.min, this.center.y) + const size = this.bounds.getSize(new Vector2()) + // convert board blocks to board data + const boardBlocks = this.iterBlocksQuery() + const data = [] for (const block of boardBlocks) { - const blockType = block.data.type + const blockType = this.isWithinBoard(block.pos) + ? block.data.type + : BlockType.NONE const blockCat = blockTypeCategoryMapper(blockType) - const boardBlock: BoardBlock = { + const boardElement: BoardBlock = { type: blockType, category: blockCat, } - this.output.data.push(boardBlock) + data.push(boardElement) + } + // DataContainer.copySourceOverTargetContainer(boardContainer, this) + const board: BoardOutputData = { origin, size, data } + return board + } + + /** + * Convert previously exported board data to ground container + * @param board exported data + * @returns ground container + */ + importBoardData(board: BoardOutputData) { + const origin = asVect2(board.origin as Vector3) + const end = origin.clone().add(board.size) + const bounds = new Box2(origin, end) + this.init(bounds) + // copy cource content over target container + const targetContainer = this // new GroundPatch(bounds) + const blocks = this.iterBlocksQuery() // BoardUtils.iterBoardData(board) + const boardLevel = board.origin.y + let index = 0 + for (const block of blocks) { + const boardData = board.data[index++] + if (boardData) { + const blockData: BlockData = { + level: boardLevel, + type: boardData.type, + mode: BlockMode.BOARD_CONTAINER, + } + targetContainer.setBlock(block.pos, blockData) + } } - const { origin, size, data } = this.output - const boardOutputData: BoardOutputData = { origin, size, data } - return boardOutputData + return targetContainer } } diff --git a/src/index.ts b/src/index.ts index 1d81e0a..88376cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,12 +7,11 @@ export { PseudoDistributionMap } from './datacontainers/RandomDistributionMap' export { BoardContainer } from './feats/BoardContainer' export { EntityType } from './common/types' export { BlockMode, GroundPatch } from './datacontainers/GroundPatch' -export { PatchesMap } from './datacontainers/PatchesMap' -export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundPatchesMap' +// export { CacheContainer as WorldCacheContainer } from './datacontainers/GroundMap' +export { GroundMap } from './datacontainers/GroundMap' 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' diff --git a/src/misc/WorldConfig.ts b/src/misc/WorldConfig.ts index d7ee1e1..2e47878 100644 --- a/src/misc/WorldConfig.ts +++ b/src/misc/WorldConfig.ts @@ -1,3 +1,5 @@ +import { Vector2 } from 'three' + import { BlockType } from '../index' export class WorldConf { @@ -12,6 +14,10 @@ export class WorldConf { return Math.pow(2, this.cachePowLimit) } + static get regularPatchDimensions() { + return new Vector2(this.patchSize, this.patchSize) + } + static defaultDistMapPeriod = 4 * WorldConf.patchSize static debug = { patch: { diff --git a/src/tools/ChunkFactory.ts b/src/tools/ChunkFactory.ts index 7364544..e5e9241 100644 --- a/src/tools/ChunkFactory.ts +++ b/src/tools/ChunkFactory.ts @@ -1,9 +1,24 @@ +import { Vector3 } from 'three' + import { PatchId } from '../common/types' -import { asVect3, chunkBoxFromId, serializeChunkId } from '../common/utils' +import { + asBox2, + asVect3, + chunkBoxFromId, + serializeChunkId, +} from '../common/utils' import { EntityChunk } from '../datacontainers/EntityChunk' import { WorldChunk, WorldChunkStub } from '../datacontainers/WorldChunk' import { BlockMode, BlockType, GroundPatch, WorldConf } from '../index' +// for debug use only +const highlightPatchBorders = (localPos: Vector3, blockType: BlockType) => { + return WorldConf.debug.patch.borderHighlightColor && + (localPos.x === 1 || localPos.z === 1) + ? WorldConf.debug.patch.borderHighlightColor + : blockType +} + export class ChunkFactory { // eslint-disable-next-line no-use-before-define static defaultInstance: ChunkFactory @@ -49,11 +64,9 @@ export class ChunkFactory { const chunkBox = chunkBoxFromId(chunkId, WorldConf.patchSize) const worldChunk = new WorldChunk(chunkBox) // Ground pass - patch.fillChunk(worldChunk) + this.mergeGroundBlocks(worldChunk, patch) // Entities pass - patchEntities.forEach(entityChunk => - patch.mergeEntityVoxels(entityChunk, worldChunk), - ) + this.mergePatchEntities(worldChunk, patch, patchEntities) const worldChunkStub: WorldChunkStub = { key: serializeChunkId(chunkId), data: worldChunk.chunkData, @@ -62,4 +75,48 @@ export class ChunkFactory { }) return worldChunksStubs } + + mergeGroundBlocks(worldChunk: WorldChunk, patch: GroundPatch) { + const blocks = patch.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 || []) + } + } + + mergePatchEntities( + worldChunk: WorldChunk, + patch: GroundPatch, + patchEntities: EntityChunk[], + ) { + patchEntities.forEach(entityChunk => { + // return overlapping blocks between entity and container + const patchBlocksIter = patch.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) + } + }) + } }