Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/battle board + heavy refactor #23

Merged
merged 45 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8e608f7
feat: extends world utils + misc cache changes
etienne-85 Aug 10, 2024
14e2977
feat: custom size blocks container, built-in patch margin
etienne-85 Aug 9, 2024
bbeb023
feat: add patch container data struct + update cache
etienne-85 Aug 9, 2024
2a1b6e1
feat: changes to support board containers, include chunk tools
etienne-85 Aug 12, 2024
7619741
feat: mega-refactor of compute API, data containers (cache,board) + b…
etienne-85 Aug 13, 2024
3506442
fix: formatting + include trailing changes
etienne-85 Aug 14, 2024
b66c6be
feat: built-in cache allow toggling on/off
etienne-85 Aug 14, 2024
23ce824
fix: defaulting to return ground (without entities) for blocks batch …
etienne-85 Aug 14, 2024
704c0c8
feat: misc changes
etienne-85 Aug 15, 2024
151e73a
fix: broken entities blocks buffer
etienne-85 Aug 15, 2024
e910316
refactor: chunk gen misc improvements
etienne-85 Aug 16, 2024
ddf3423
feat: low-level data encoding/decoding for block storage
etienne-85 Aug 22, 2024
bb81308
feat: chunk making refactor, entities blocks iteration
etienne-85 Aug 25, 2024
1c6fd6a
refactor: local/global pos,
etienne-85 Aug 25, 2024
ea41cd3
feat: board entities triming
etienne-85 Aug 26, 2024
4a0c085
fix: style/type, code cleanup
etienne-85 Aug 26, 2024
94d4ae2
fix: type
etienne-85 Aug 26, 2024
3bfce28
fix: board margin blocks
etienne-85 Aug 26, 2024
3a4d631
refactor: entities + defaults to using global pos
etienne-85 Aug 27, 2024
7c57715
refactor: entities chunkification isolation
etienne-85 Aug 28, 2024
0b07176
refactor: generic distribution map
etienne-85 Aug 28, 2024
d65f511
wip: start pos distribution + refactor data containers
etienne-85 Aug 29, 2024
3a3a925
refactor: renaming, splitting, switch to Box2 wherever appropriate
etienne-85 Aug 29, 2024
3c17d9d
feat: board start positions random distribution (including support f…
etienne-85 Aug 29, 2024
714adb1
refactor: misc (split commit #1)
etienne-85 Aug 30, 2024
e81426a
refactor: end split commit
etienne-85 Aug 30, 2024
4e5c475
feat: holes digging
etienne-85 Aug 30, 2024
6bf1d99
fix: lint/types
etienne-85 Aug 30, 2024
f3da0ff
fix: remove checkerboard excess on board edges and entities
etienne-85 Aug 30, 2024
39b6b84
refactor: WorldConf: move debug vars to global conf
etienne-85 Aug 30, 2024
fa79b72
fix: reduce distribution map queries slowdowns
etienne-85 Aug 31, 2024
3304244
feat: board data export and code cleanup
etienne-85 Aug 31, 2024
e6d48ab
refactor: merge worker api into unique compute api with optional work…
etienne-85 Sep 1, 2024
726f035
refactor: bake entities separately from ground patch + misc other cha…
etienne-85 Sep 1, 2024
f1432a9
refactor: chunkification + misc
etienne-85 Sep 4, 2024
d7f7bf3
feat: functional board container with worker support + use low-level …
etienne-85 Sep 4, 2024
ce464ca
fix: support margin blocks in low level data copy
etienne-85 Sep 4, 2024
330a75b
fix: restore all board functionalities
etienne-85 Sep 4, 2024
4a5d51f
fix: init and data indexing troublesome issues
etienne-85 Sep 5, 2024
1b2c5dc
feat: varying board hole areas + export format refactor
etienne-85 Sep 6, 2024
4dce24d
feat: now using compute proxy for built-in patch filling + refactor …
etienne-85 Sep 7, 2024
5cafa36
fix: board export format, minor refactor
etienne-85 Sep 9, 2024
d600d06
refactor: misc
etienne-85 Sep 10, 2024
b44b358
fix: types, formatting
etienne-85 Sep 10, 2024
3a0886b
fix: accidental commit roll back + fix wrong type
etienne-85 Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions src/api/WorldComputeProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { Box2, Vector3 } from 'three'

import { Block, EntityData, PatchKey } from '../common/types'
import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk'
import { GroundPatch, WorldCompute, WorldUtils } from '../index'
import { parseThreeStub } from '../common/utils'

export enum ComputeApiCall {
PatchCompute = 'bakeGroundPatch',
BlocksBatchCompute = 'computeBlocksBatch',
OvergroundBufferCompute = 'computeOvergroundBuffer',
QueryEntities = 'queryEntities',
BakeEntities = 'queryBakeEntities',
BattleBoardCompute = 'computeBoardData',
}

export type ComputeApiParams = Partial<{
rememberMe: boolean // allow for caching value
preCacheRadius: number // pre-caching next requests
includeEntitiesBlocks: boolean // skip or include entities blocks
}>

/**
* Exposing world compute api with ability to run inside optional worker
* When provided all request are proxied to worker instead of main thread
*/
export class WorldComputeProxy {
// eslint-disable-next-line no-use-before-define
static singleton: WorldComputeProxy
// eslint-disable-next-line no-undef
workerInstance: Worker | undefined
resolvers: Record<number, any> = {}
count = 0

static get instance() {
this.singleton = this.singleton || new WorldComputeProxy()
return this.singleton
}

get worker() {
return this.workerInstance
}

// eslint-disable-next-line no-undef
set worker(workerInstance: Worker | undefined) {
this.workerInstance = workerInstance
if (workerInstance) {
workerInstance.onmessage = ({ data }) => {
if (data.id !== undefined) {
this.resolvers[data.id]?.(data.data)
delete this.resolvers[data.id]
}
}

workerInstance.onerror = error => {
console.error(error)
}

workerInstance.onmessageerror = error => {
console.error(error)
}
}
}

/**
* Proxying request to worker
*/
workerCall(apiName: ComputeApiCall, args: any[]) {
if (this.worker) {
const id = this.count++
this.worker.postMessage({ id, apiName, args })
return new Promise<any>(resolve => (this.resolvers[id] = resolve))
}
return null
}

async computeBlocksBatch(
blockPosBatch: Vector3[],
params = { includeEntitiesBlocks: false },
) {
const blocks = !this.worker
? WorldCompute.computeBlocksBatch(blockPosBatch, params)
: ((await this.workerCall(ComputeApiCall.BlocksBatchCompute, [
blockPosBatch,
params,
])?.then((blocksStubs: Block[]) =>
// parse worker's data to recreate original objects
blocksStubs.map(blockStub => {
blockStub.pos = WorldUtils.parseThreeStub(blockStub.pos)
return blockStub
}),
)) as Block[])

return blocks
}

// *iterEntitiesBaking(entityKeys: EntityKey[]) {
// for (const entityKey of entityKeys) {
// const entityChunk = WorldCompute.bakeChunkEntity(entityKey)
// yield entityChunk
// }
// }

async queryEntities(queriedRegion: Box2) {
const entitiesData = !this.worker
? WorldCompute.queryEntities(queriedRegion)
: ((await this.workerCall(
ComputeApiCall.QueryEntities,
[queriedRegion], // [emptyPatch.bbox]
)?.then(stubs =>
stubs.map((stub: EntityData) => ({
...stub,
bbox: parseThreeStub(stub.bbox),
})),
)) as EntityData[])
return entitiesData
}

async *iterPatchCompute(patchKeysBatch: PatchKey[]) {
for (const patchKey of patchKeysBatch) {
const patch = !this.worker
? WorldCompute.bakeGroundPatch(patchKey)
: ((await this.workerCall(
ComputeApiCall.PatchCompute,
[patchKey], // [emptyPatch.bbox]
)?.then(patchStub =>
new GroundPatch().fromStub(patchStub),
)) as GroundPatch)

yield patch
}
}

async bakeGroundPatch(boundsOrPatchKey: Box2 | string) {
const patchStub = !this.worker
? WorldCompute.bakeGroundPatch(boundsOrPatchKey)
: await this.workerCall(ComputeApiCall.PatchCompute, [boundsOrPatchKey])
// ?.then(patchStub => new GroundPatch().fromStub(patchStub)) as GroundPatch

return patchStub
}

async bakeEntities(queriedRange: Box2) {
const entityChunks = !this.worker
? WorldCompute.queryBakeEntities(queriedRange)
: await this.workerCall(ComputeApiCall.BakeEntities, [
queriedRange,
])?.then((entityChunks: EntityChunkStub[]) =>
// parse worker's data to recreate original objects
entityChunks.map(chunkStub => EntityChunk.fromStub(chunkStub)),
)
return entityChunks
}

// async requestBattleBoard(boardCenter: Vector3, boardParams: BoardParams, lastBoardBounds: Box2) {
// const boardData = !this.worker ?
// WorldCompute.computeBoardData(boardCenter, boardParams, lastBoardBounds) :
// await this.workerCall(
// ComputeApiCall.BattleBoardCompute,
// [boardCenter, boardParams, lastBoardBounds],
// )
// const board = new BoardContainer().fromStub(boardData)
// return board
// }
}
180 changes: 180 additions & 0 deletions src/api/world-compute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { Box2, Vector3 } from 'three'

import { EntityType, GroundPatch } from '../index'
import { Biome, BlockType } from '../procgen/Biome'
import { Heightmap } from '../procgen/Heightmap'
import { Block, EntityData, PatchKey } from '../common/types'
import { asBox3, asVect2, asVect3 } from '../common/utils'
import { WorldEntities } from '../procgen/WorldEntities'
import { EntityChunk, EntityChunkStub } from '../datacontainers/EntityChunk'
import { BlockData } from '../datacontainers/GroundPatch'
// import { BoardInputParams } from '../feats/BoardContainer'

/**
* Individual blocks requests
*/

/**
*
* @param blockPosBatch
* @param params
* @returns
*/
export const computeBlocksBatch = (
blockPosBatch: Vector3[],
params = { includeEntitiesBlocks: false },
) => {
const { includeEntitiesBlocks } = params
const blocksBatch = blockPosBatch.map(({ x, z }) => {
const blockPos = new Vector3(x, 0, z)
const blockData = computeGroundBlock(blockPos)
if (includeEntitiesBlocks) {
const entityRange = new Box2().setFromPoints([asVect2(blockPos)])
entityRange.max.addScalar(1)
const [foundEntity] = queryEntities(entityRange).map(entityData => {
const { min, max } = entityData.bbox
const custChunkBox = asBox3(entityRange)
custChunkBox.min.y = min.y
custChunkBox.max.y = max.y
return new EntityChunk(entityData, custChunkBox)
})
const blocksBuffer = foundEntity?.voxelize()
const lastBlockIndex = blocksBuffer?.findLastIndex(elt => elt)
if (blocksBuffer && lastBlockIndex && lastBlockIndex >= 0) {
blockData.level += lastBlockIndex
blockData.type = blocksBuffer[lastBlockIndex] as BlockType
}
}
blockPos.y = blockData.level
const block: Block = {
pos: blockPos,
data: blockData,
}
return block
})
return blocksBatch
}

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 block: BlockData = { level, type }
return block
}

/**
* Patch requests
*/

// Ground
export const bakeGroundPatch = (boundsOrPatchKey: PatchKey | Box2) => {
const groundPatch = new GroundPatch(boundsOrPatchKey)
const { min, max } = groundPatch.bounds
const blocks = groundPatch.iterBlocksQuery(undefined, false)
const level = {
min: 512,
max: 0,
}
let blockIndex = 0
for (const block of blocks) {
const blockData = computeGroundBlock(block.pos)
level.min = Math.min(min.y, blockData.level)
level.max = Math.max(max.y, blockData.level)
groundPatch.writeBlockData(blockIndex, blockData)
blockIndex++
}
return groundPatch
}

// Battle board
// export const computeBoardData = (boardPos: Vector3, boardParams: BoardInputParams, lastBoardBounds: Box2) => {
// const boardMap = new BoardContainer(boardPos, boardParams, lastBoardBounds)
// await boardMap.fillGroundData()
// await boardMap.populateEntities()
// const boardStub = boardMap.toStub()
// return boardStub
// }

/**
* Entity queries/baking
*/

export const queryEntities = (queriedRegion: Box2) => {
const spawnablePlaces = WorldEntities.instance.queryDistributionMap(
EntityType.TREE_APPLE,
)(queriedRegion)
const spawnedEntities = spawnablePlaces
.map(entLoc =>
WorldEntities.instance.getEntityData(
EntityType.TREE_PINE,
asVect3(entLoc),
),
)
.filter(entity => confirmFinalizeEntity(entity))
return spawnedEntities
}

/**
*
* @param entityPos
* @returns
*/
const confirmFinalizeEntity = (entity: EntityData) => {
const entityPos = entity.bbox.getCenter(new Vector3())
// use global coords in case entity center is from adjacent patch
const rawVal = Heightmap.instance.getRawVal(entityPos)
const mainBiome = Biome.instance.getMainBiome(entityPos)
const blockTypes = Biome.instance.getBlockType(rawVal, mainBiome)
const entityType = blockTypes.entities?.[0] as EntityType
// confirm this kind of entity can spawn over here
if (entityType) {
entity.bbox.min.y = Heightmap.instance.getGroundLevel(entityPos, rawVal)
entity.bbox.max.y += entity.bbox.min.y
return entity
}
return null
}

export const queryBakeEntities = (queriedRange: Box2) => {
const entitiesData = queryEntities(queriedRange)
return bakeEntitiesBatch(entitiesData)
}

export const bakeEntitiesBatch = (entities: EntityData[]) => {
const entitiesChunks: EntityChunkStub[] = entities
.map(entityData => new EntityChunk(entityData))
.map(entityChunk => {
entityChunk.voxelize()
return entityChunk.toStub()
})
return entitiesChunks
}

// /**
// * return all entity types which can spwawn over specific region
// */
// const getSpawnableEntities = (region: Box2) => {
// // TODO
// }

// /**
// * granular check in transition place (spline or biome transitions)
// */
// const confirmSpawnability = () => {

// }
19 changes: 19 additions & 0 deletions src/common/math.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Box2, Vector2 } from 'three'

export const findBoundingBox = (
point: Vector2,
points: Vector2[],
bounds: Box2,
) => {
const { min, max } = bounds.clone()

for (const p of points) {
min.x = p.x < point.x ? Math.max(p.x, min.x) : min.x
min.y = p.y < point.y ? Math.max(p.y, min.y) : min.y
max.x = p.x > point.x ? Math.min(p.x, max.x) : max.x
max.y = p.y > point.y ? Math.min(p.y, max.y) : max.y
}

const bbox = new Box2(min, max)
return bbox
}
Loading