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 - } -}