Skip to content

Commit

Permalink
feat: mega-refactor of compute API, data containers (cache,board) + b…
Browse files Browse the repository at this point in the history
…uilt-in chunk export
  • Loading branch information
etienne-85 committed Aug 14, 2024
1 parent 2a1b6e1 commit 7619741
Show file tree
Hide file tree
Showing 14 changed files with 654 additions and 550 deletions.
12 changes: 11 additions & 1 deletion src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Vector3 } from 'three'
import { Vector2, Vector3 } from 'three'

import { BiomeType, BlockType } from '../procgen/Biome'

Expand Down Expand Up @@ -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
}
48 changes: 45 additions & 3 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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,
Expand All @@ -232,5 +269,10 @@ export {
bboxContainsPointXZ,
getPatchPoints,
parseThreeStub,
asVect3,
vect2ToVect3,
vect3ToVect2,
parseChunkKey,
serializeChunkId,
genChunkIds,
getChunkBboxFromId
}
123 changes: 123 additions & 0 deletions src/compute/WorldComputeApi.ts
Original file line number Diff line number Diff line change
@@ -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<BlockData[]>
// computePatch(patchKey: PatchKey): BlocksPatch | Promise<BlocksPatch>
iterPatchCompute(patchKeysBatch: PatchKey[]): Generator<BlocksPatch, void, unknown> | AsyncGenerator<BlocksPatch, void, unknown>
}

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<number, any> = {}

// 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<any>(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
}
}
}
154 changes: 154 additions & 0 deletions src/compute/world-compute.ts
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 7619741

Please sign in to comment.