From 347d155884bd2004c4b4e369cd173f77a068ef43 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 22 Oct 2024 19:28:41 +0300 Subject: [PATCH] fix: improve playground by allowing sync world for fast iterating of advanced use cases --- prismarine-viewer/examples/baseScene.ts | 99 ++++++++++++++----- prismarine-viewer/examples/scenes/main.ts | 2 +- prismarine-viewer/examples/shared.ts | 65 +++++++++++- prismarine-viewer/playground.html | 3 + prismarine-viewer/viewer/lib/viewer.ts | 3 + .../viewer/lib/worldDataEmitter.ts | 13 +++ 6 files changed, 157 insertions(+), 28 deletions(-) diff --git a/prismarine-viewer/examples/baseScene.ts b/prismarine-viewer/examples/baseScene.ts index aca6e3ed..ec2db6e8 100644 --- a/prismarine-viewer/examples/baseScene.ts +++ b/prismarine-viewer/examples/baseScene.ts @@ -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 void> @@ -43,6 +46,7 @@ export class BasePlaygroundScene { skipUpdateQs = false controls: any windowHidden = false + world: ReturnType 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> + 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) diff --git a/prismarine-viewer/examples/scenes/main.ts b/prismarine-viewer/examples/scenes/main.ts index 7b1b89b9..1fe801ee 100644 --- a/prismarine-viewer/examples/scenes/main.ts +++ b/prismarine-viewer/examples/scenes/main.ts @@ -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() diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts index d7a402dd..ba58a57f 100644 --- a/prismarine-viewer/examples/shared.ts +++ b/prismarine-viewer/examples/shared.ts @@ -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 (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) + } +} diff --git a/prismarine-viewer/playground.html b/prismarine-viewer/playground.html index c8ea00d2..ec4c0f33 100644 --- a/prismarine-viewer/playground.html +++ b/prismarine-viewer/playground.html @@ -28,6 +28,9 @@ font-family: mojangles; src: url(../../../assets/mojangles.ttf); } + * { + user-select: none; + }