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: Finally a light engine! #304

Open
wants to merge 7 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@
"timers-browserify": "^2.0.12",
"typescript": "5.5.4",
"vitest": "^0.34.6",
"yaml": "^2.3.2"
"yaml": "^2.3.2",
"minecraft-lighting": "^0.0.4"
},
"optionalDependencies": {
"cypress": "^10.11.0",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions renderer/viewer/lib/lightEngine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, fillColumnWithZeroLight } from 'minecraft-lighting'
import { world } from 'prismarine-world'

let lightEngine: LightWorld | null = null
export const getLightEngine = () => {
if (!lightEngine) throw new Error('Light engine not initialized')
return lightEngine
}

export const createLightEngine = () => {
lightEngine = createLightEngineForSyncWorld(worldView!.world as unknown as world.WorldSync, loadedData, {
minY: viewer.world.worldConfig.minY,
height: viewer.world.worldConfig.worldHeight,
enableSkyLight: false,
})
globalThis.lightEngine = lightEngine
}

export const processLightChunk = async (x: number, z: number) => {
const chunkX = Math.floor(x / 16)
const chunkZ = Math.floor(z / 16)
const engine = getLightEngine()
fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ)
return engine.receiveUpdateColumn(chunkX, chunkZ)
}

export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => {
const engine = getLightEngine()
engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(mcData.blocks[stateId], mcData))
}
33 changes: 19 additions & 14 deletions renderer/viewer/lib/mesher/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class World {
}

getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
const IS_USING_SERVER_LIGHTING = false
// for easier testing
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
const { enableLighting, skyLight } = this.config
Expand All @@ -64,24 +65,28 @@ export class World {
Math.max(
column.getBlockLight(posInChunk(pos)),
Math.min(skyLight, column.getSkyLight(posInChunk(pos)))
) + 2
)
)
const MIN_LIGHT_LEVEL = 2
result = Math.max(result, MIN_LIGHT_LEVEL)
// lightsCache.set(key, result)
if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
const lights = [
this.getLight(pos.offset(0, 1, 0), undefined, true),
this.getLight(pos.offset(0, -1, 0), undefined, true),
this.getLight(pos.offset(0, 0, 1), undefined, true),
this.getLight(pos.offset(0, 0, -1), undefined, true),
this.getLight(pos.offset(1, 0, 0), undefined, true),
this.getLight(pos.offset(-1, 0, 0), undefined, true)
].filter(x => x !== 2)
if (lights.length) {
const min = Math.min(...lights)
result = min
if (result === 2 && IS_USING_SERVER_LIGHTING) {
if ([this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
const lights = [
this.getLight(pos.offset(0, 1, 0), undefined, true),
this.getLight(pos.offset(0, -1, 0), undefined, true),
this.getLight(pos.offset(0, 0, 1), undefined, true),
this.getLight(pos.offset(0, 0, -1), undefined, true),
this.getLight(pos.offset(1, 0, 0), undefined, true),
this.getLight(pos.offset(-1, 0, 0), undefined, true)
].filter(x => x !== 2)
if (lights.length) {
const min = Math.min(...lights)
result = min
}
}
if (isNeighbor) result = 15 // TODO
}
if (isNeighbor && result === 2) result = 15 // TODO
return result
}

Expand Down
18 changes: 15 additions & 3 deletions renderer/viewer/lib/worldDataEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { EventEmitter } from 'events'
import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils'
import { Vec3 } from 'vec3'
import { BotEvents } from 'mineflayer'
import { getItemFromBlock } from '../../../src/chatUtils'
import { delayedIterator } from '../../playground/shared'
import { playerState } from '../../../src/mineflayer/playerState'
import { chunkPos } from './simpleUtils'
import { createLightEngine, processLightChunk, updateBlockLight } from './lightEngine'

export type ChunkPosKey = string
type ChunkPos = { x: number, z: number }
Expand Down Expand Up @@ -52,6 +52,7 @@ export class WorldDataEmitter extends EventEmitter {
return
}

updateBlockLight(position.x, position.y, position.z, stateId)
this.emit('blockUpdate', { pos: position, stateId })
}

Expand Down Expand Up @@ -145,6 +146,7 @@ export class WorldDataEmitter extends EventEmitter {
}

async init (pos: Vec3) {
createLightEngine()
this.updateViewDistance(this.viewDistance)
this.emitter.emit('chunkPosUpdate', { pos })
const [botX, botZ] = chunkPos(pos)
Expand Down Expand Up @@ -177,12 +179,22 @@ export class WorldDataEmitter extends EventEmitter {

async loadChunk (pos: ChunkPos, isLightUpdate = false) {
const [botX, botZ] = chunkPos(this.lastPos)
const dx = Math.abs(botX - Math.floor(pos.x / 16))
const dz = Math.abs(botZ - Math.floor(pos.z / 16))
const chunkX = Math.floor(pos.x / 16)
const chunkZ = Math.floor(pos.z / 16)
const dx = Math.abs(botX - chunkX)
const dz = Math.abs(botZ - chunkZ)
if (dx <= this.viewDistance && dz <= this.viewDistance) {
// eslint-disable-next-line @typescript-eslint/await-thenable -- todo allow to use async world provider but not sure if needed
const column = await this.world.getColumnAt(pos['y'] ? pos as Vec3 : new Vec3(pos.x, 0, pos.z))
if (column) {
const result = await processLightChunk(pos.x, pos.z)
if (!result) return
for (const affectedChunk of result) {
if (affectedChunk.x === chunkX && affectedChunk.z === chunkZ) continue
const loadedChunk = this.loadedChunks[`${affectedChunk.x},${affectedChunk.z}`]
if (!loadedChunk) continue
void this.loadChunk(new Vec3(affectedChunk.x * 16, 0, affectedChunk.z * 16), true)
}
// const latency = Math.floor(performance.now() - this.lastTime)
// this.debugGotChunkLatency.push(latency)
// this.lastTime = performance.now()
Expand Down
2 changes: 1 addition & 1 deletion src/optionsStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const defaultOptions = {
showCursorBlockInSpectator: false,
renderEntities: true,
smoothLighting: true,
newVersionsLighting: false,
newVersionsLighting: true,
chatSelect: true,
autoJump: 'auto' as 'auto' | 'always' | 'never',
autoParkour: false,
Expand Down
Loading