fix: improve playground by allowing sync world for fast iterating of advanced use cases
This commit is contained in:
parent
70867564ed
commit
347d155884
6 changed files with 157 additions and 28 deletions
|
|
@ -17,6 +17,7 @@ import { WorldDataEmitter } from '../viewer'
|
|||
import { Viewer } from '../viewer/lib/viewer'
|
||||
import { BlockNames } from '../../src/mcDataTypes'
|
||||
import { initWithRenderer, statsEnd, statsStart } from '../../src/topRightStats'
|
||||
import { getSyncWorld } from './shared'
|
||||
|
||||
window.THREE = THREE
|
||||
|
||||
|
|
@ -31,11 +32,13 @@ export class BasePlaygroundScene {
|
|||
options?: string[]
|
||||
min?: number
|
||||
max?: number
|
||||
reloadOnChange?: boolean
|
||||
}>>
|
||||
version = new URLSearchParams(window.location.search).get('version') || globalThis.includedVersions.at(-1)
|
||||
Chunk: typeof import('prismarine-chunk/types/index').PCChunk
|
||||
Block: typeof import('prismarine-block').Block
|
||||
ignoreResize = false
|
||||
enableCameraControls = true // not finished
|
||||
enableCameraOrbitControl = true
|
||||
gui = new GUI()
|
||||
onParamUpdate = {} as Record<string, () => void>
|
||||
|
|
@ -43,6 +46,7 @@ export class BasePlaygroundScene {
|
|||
skipUpdateQs = false
|
||||
controls: any
|
||||
windowHidden = false
|
||||
world: ReturnType<typeof getSyncWorld>
|
||||
|
||||
constructor () {
|
||||
void this.initData().then(() => {
|
||||
|
|
@ -90,6 +94,12 @@ export class BasePlaygroundScene {
|
|||
if (object === this.params) {
|
||||
this.onParamUpdate[property]?.()
|
||||
this.onParamsUpdate(property, object)
|
||||
const value = this.params[property]
|
||||
if (this.paramOptions[property]?.reloadOnChange && (typeof value === 'boolean' || this.paramOptions[property].options)) {
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.onParamsUpdate(property, object)
|
||||
}
|
||||
|
|
@ -97,7 +107,7 @@ export class BasePlaygroundScene {
|
|||
})
|
||||
}
|
||||
|
||||
mainChunk: import('prismarine-chunk/types/index').PCChunk
|
||||
// mainChunk: import('prismarine-chunk/types/index').PCChunk
|
||||
|
||||
setupWorld () { }
|
||||
|
||||
|
|
@ -107,13 +117,13 @@ export class BasePlaygroundScene {
|
|||
const block =
|
||||
properties ?
|
||||
this.Block.fromProperties(loadedData.blocksByName[blockName].id, properties ?? {}, 0) :
|
||||
this.Block.fromStateId(loadedData.blocksByName[blockName].defaultState, 0)
|
||||
this.mainChunk.setBlock(this.targetPos.offset(xOffset, yOffset, zOffset), block)
|
||||
this.Block.fromStateId(loadedData.blocksByName[blockName].defaultState!, 0)
|
||||
this.world.setBlock(this.targetPos.offset(xOffset, yOffset, zOffset), block)
|
||||
}
|
||||
|
||||
resetCamera () {
|
||||
const { targetPos } = this
|
||||
this.controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
|
||||
this.controls?.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
|
||||
|
||||
const cameraPos = targetPos.offset(2, 2, 2)
|
||||
const pitch = THREE.MathUtils.degToRad(-45)
|
||||
|
|
@ -121,7 +131,7 @@ export class BasePlaygroundScene {
|
|||
viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX')
|
||||
viewer.camera.lookAt(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5)
|
||||
viewer.camera.position.set(cameraPos.x + 0.5, cameraPos.y + 0.5, cameraPos.z + 0.5)
|
||||
this.controls.update()
|
||||
this.controls?.update()
|
||||
}
|
||||
|
||||
async initData () {
|
||||
|
|
@ -132,29 +142,33 @@ export class BasePlaygroundScene {
|
|||
this.Chunk = (ChunkLoader as any)(this.version)
|
||||
this.Block = (BlockLoader as any)(this.version)
|
||||
|
||||
this.mainChunk = new this.Chunk(undefined as any)
|
||||
const World = (WorldLoader as any)(this.version)
|
||||
const world = new World((chunkX, chunkZ) => {
|
||||
if (chunkX === 0 && chunkZ === 0) return this.mainChunk
|
||||
return new this.Chunk(undefined as any)
|
||||
})
|
||||
const world = getSyncWorld(this.version)
|
||||
world.setBlockStateId(this.targetPos, 0)
|
||||
this.world = world
|
||||
|
||||
this.initGui()
|
||||
|
||||
const worldView = new WorldDataEmitter(world, this.viewDistance, this.targetPos)
|
||||
worldView.addWaitTime = 0
|
||||
window.worldView = worldView
|
||||
|
||||
// Create three.js context, add to page
|
||||
const renderer = new THREE.WebGLRenderer({ alpha: true, ...localStorage['renderer'] })
|
||||
renderer.setPixelRatio(window.devicePixelRatio || 1)
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
initWithRenderer(renderer.domElement)
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
// Create viewer
|
||||
const viewer = new Viewer(renderer, { numWorkers: 1, showChunkBorders: false, })
|
||||
const viewer = new Viewer(renderer, { numWorkers: 6, showChunkBorders: false, })
|
||||
window.viewer = viewer
|
||||
viewer.world.isPlayground = true
|
||||
const isWebgpu = false
|
||||
const promises = [] as Array<Promise<void>>
|
||||
if (isWebgpu) {
|
||||
// promises.push(initWebgpuRenderer(() => { }, true, true)) // todo
|
||||
} else {
|
||||
initWithRenderer(renderer.domElement)
|
||||
renderer.domElement.id = 'viewer-canvas'
|
||||
document.body.appendChild(renderer.domElement)
|
||||
}
|
||||
viewer.addChunksBatchWaitTime = 0
|
||||
viewer.world.blockstatesModels = blockstatesModels
|
||||
viewer.entities.setDebugMode('basic')
|
||||
|
|
@ -163,15 +177,17 @@ export class BasePlaygroundScene {
|
|||
viewer.render()
|
||||
}
|
||||
viewer.world.mesherConfig.enableLighting = false
|
||||
await Promise.all(promises)
|
||||
this.setupWorld()
|
||||
|
||||
viewer.connect(worldView)
|
||||
|
||||
await worldView.init(this.targetPos)
|
||||
|
||||
if (this.enableCameraOrbitControl) {
|
||||
if (this.enableCameraControls) {
|
||||
const { targetPos } = this
|
||||
const controls = new OrbitControls(viewer.camera, renderer.domElement)
|
||||
const canvas = document.querySelector('#viewer-canvas')
|
||||
const controls = this.enableCameraOrbitControl ? new OrbitControls(viewer.camera, canvas) : undefined
|
||||
this.controls = controls
|
||||
|
||||
this.resetCamera()
|
||||
|
|
@ -182,7 +198,7 @@ export class BasePlaygroundScene {
|
|||
const [x, y, z, rx, ry] = cameraSet.split(',').map(Number)
|
||||
viewer.camera.position.set(x, y, z)
|
||||
viewer.camera.rotation.set(rx, ry, 0, 'ZYX')
|
||||
controls.update()
|
||||
this.controls?.update()
|
||||
}
|
||||
const throttledCamQsUpdate = _.throttle(() => {
|
||||
const { camera } = viewer
|
||||
|
|
@ -196,13 +212,46 @@ export class BasePlaygroundScene {
|
|||
camera.rotation.y.toFixed(2),
|
||||
].join(',')
|
||||
}, 200)
|
||||
controls.addEventListener('change', () => {
|
||||
throttledCamQsUpdate()
|
||||
this.render()
|
||||
})
|
||||
if (this.controls) {
|
||||
this.controls.addEventListener('change', () => {
|
||||
throttledCamQsUpdate()
|
||||
this.render()
|
||||
})
|
||||
} else {
|
||||
setInterval(() => {
|
||||
throttledCamQsUpdate()
|
||||
}, 200)
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
|
||||
if (!this.enableCameraOrbitControl) {
|
||||
// mouse
|
||||
let mouseMoveCounter = 0
|
||||
const mouseMove = (e: PointerEvent) => {
|
||||
if ((e.target as HTMLElement).closest('.lil-gui')) return
|
||||
if (e.buttons === 1 || e.pointerType === 'touch') {
|
||||
mouseMoveCounter++
|
||||
viewer.camera.rotation.x -= e.movementY / 100
|
||||
//viewer.camera.
|
||||
viewer.camera.rotation.y -= e.movementX / 100
|
||||
if (viewer.camera.rotation.x < -Math.PI / 2) viewer.camera.rotation.x = -Math.PI / 2
|
||||
if (viewer.camera.rotation.x > Math.PI / 2) viewer.camera.rotation.x = Math.PI / 2
|
||||
|
||||
// yaw += e.movementY / 20;
|
||||
// pitch += e.movementX / 20;
|
||||
}
|
||||
if (e.buttons === 2) {
|
||||
viewer.camera.position.set(0, 0, 0)
|
||||
}
|
||||
}
|
||||
setInterval(() => {
|
||||
// updateTextEvent(`Mouse Events: ${mouseMoveCounter}`)
|
||||
mouseMoveCounter = 0
|
||||
}, 1000)
|
||||
window.addEventListener('pointermove', mouseMove)
|
||||
}
|
||||
|
||||
// await this.initialSetup()
|
||||
this.onResize()
|
||||
window.addEventListener('resize', () => this.onResize())
|
||||
|
|
@ -233,7 +282,7 @@ export class BasePlaygroundScene {
|
|||
addKeyboardShortcuts () {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'KeyR' && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
||||
this.controls.reset()
|
||||
this.controls?.reset()
|
||||
this.resetCamera()
|
||||
}
|
||||
})
|
||||
|
|
@ -280,8 +329,8 @@ export class BasePlaygroundScene {
|
|||
direction.z *= 2
|
||||
}
|
||||
// Add the vector to the camera's position to move the camera
|
||||
viewer.camera.position.add(direction)
|
||||
this.controls.update()
|
||||
viewer.camera.position.add(direction.normalize())
|
||||
this.controls?.update()
|
||||
this.render()
|
||||
}
|
||||
setInterval(updateKeys, 1000 / 30)
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ class MainScene extends BasePlaygroundScene {
|
|||
}
|
||||
}
|
||||
|
||||
viewer.setBlockStateId(this.targetPos, block.stateId!)
|
||||
worldView!.setBlockStateId(this.targetPos, block.stateId!)
|
||||
console.log('up stateId', block.stateId)
|
||||
this.params.metadata = block.metadata
|
||||
this.metadataGui.updateDisplay()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import WorldLoader, { world } from 'prismarine-world'
|
||||
import ChunkLoader from 'prismarine-chunk'
|
||||
|
||||
export type BlockFaceType = {
|
||||
side: number
|
||||
textureIndex: number
|
||||
|
|
@ -5,8 +8,8 @@ export type BlockFaceType = {
|
|||
isTransparent?: boolean
|
||||
|
||||
// for testing
|
||||
face: string
|
||||
neighbor: string
|
||||
face?: string
|
||||
neighbor?: string
|
||||
light?: number
|
||||
}
|
||||
|
||||
|
|
@ -16,3 +19,61 @@ export type BlockType = {
|
|||
// for testing
|
||||
block: string
|
||||
}
|
||||
|
||||
export const makeError = (str: string) => {
|
||||
reportError?.(str)
|
||||
}
|
||||
export const makeErrorCritical = (str: string) => {
|
||||
throw new Error(str)
|
||||
}
|
||||
|
||||
export const getSyncWorld = (version: string): world.WorldSync => {
|
||||
const World = (WorldLoader as any)(version)
|
||||
const Chunk = (ChunkLoader as any)(version)
|
||||
|
||||
const world = new World(version).sync
|
||||
|
||||
const methods = getAllMethods(world)
|
||||
for (const method of methods) {
|
||||
if (method.startsWith('set') && method !== 'setColumn') {
|
||||
const oldMethod = world[method].bind(world)
|
||||
world[method] = (...args) => {
|
||||
const arg = args[0]
|
||||
if (arg.x !== undefined && !world.getColumnAt(arg)) {
|
||||
world.setColumn(Math.floor(arg.x / 16), Math.floor(arg.z / 16), new Chunk(undefined as any))
|
||||
}
|
||||
oldMethod(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return world
|
||||
}
|
||||
|
||||
function getAllMethods (obj) {
|
||||
const methods = new Set()
|
||||
let currentObj = obj
|
||||
|
||||
do {
|
||||
for (const name of Object.getOwnPropertyNames(currentObj)) {
|
||||
if (typeof obj[name] === 'function' && name !== 'constructor') {
|
||||
methods.add(name)
|
||||
}
|
||||
}
|
||||
} while ((currentObj = Object.getPrototypeOf(currentObj)))
|
||||
|
||||
return [...methods] as string[]
|
||||
}
|
||||
|
||||
export const delayedIterator = async <T> (arr: T[], delay: number, exec: (item: T, index: number) => void, chunkSize = 1) => {
|
||||
// if delay is 0 then don't use setTimeout
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
if (delay) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, delay)
|
||||
})
|
||||
}
|
||||
exec(arr[i], i)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@
|
|||
font-family: mojangles;
|
||||
src: url(../../../assets/mojangles.ttf);
|
||||
}
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
if (window.location.pathname.endsWith('playground')) {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ export class Viewer {
|
|||
}
|
||||
|
||||
setBlockStateId (pos: Vec3, stateId: number) {
|
||||
if (!this.world.loadedChunks[`${Math.floor(pos.x / 16)},${Math.floor(pos.z / 16)}`]) {
|
||||
console.warn('[should be unreachable] setBlockStateId called for unloaded chunk', pos)
|
||||
}
|
||||
this.world.setBlockStateId(pos, stateId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,19 @@ export class WorldDataEmitter extends EventEmitter {
|
|||
})
|
||||
}
|
||||
|
||||
setBlockStateId (position: Vec3, stateId: number) {
|
||||
const val = this.world.setBlockStateId(position, stateId) as Promise<void> | void
|
||||
if (val) throw new Error('setBlockStateId returned promise (not supported)')
|
||||
const chunkX = Math.floor(position.x / 16)
|
||||
const chunkZ = Math.floor(position.z / 16)
|
||||
if (!this.loadedChunks[`${chunkX},${chunkZ}`]) {
|
||||
void this.loadChunk({ x: chunkX, z: chunkZ })
|
||||
return
|
||||
}
|
||||
|
||||
this.emit('blockUpdate', { pos: position, stateId })
|
||||
}
|
||||
|
||||
updateViewDistance (viewDistance: number) {
|
||||
this.viewDistance = viewDistance
|
||||
this.emitter.emit('renderDistance', viewDistance)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue