From 163290d8415b825a12b1dac3f576382abf110981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Fri, 1 Mar 2024 03:03:44 +0300 Subject: [PATCH 01/86] Stash Co-authored-by: Vitaly --- .../examples/_FragmentShader.frag | 7 + prismarine-viewer/examples/_VertexShader.vert | 11 + prismarine-viewer/examples/playground.ts | 346 ++++++++++++------ prismarine-viewer/viewer/lib/viewer.ts | 35 +- prismarine-viewer/viewer/lib/worldrenderer.ts | 154 ++++---- 5 files changed, 345 insertions(+), 208 deletions(-) create mode 100644 prismarine-viewer/examples/_FragmentShader.frag create mode 100644 prismarine-viewer/examples/_VertexShader.vert diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag new file mode 100644 index 000000000..f99b66f7b --- /dev/null +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -0,0 +1,7 @@ +varying vec2 vUv; + +void main() { + + gl_FragColor = vec4(0.5f,5.0f,0.0f,1.0f); + +} \ No newline at end of file diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert new file mode 100644 index 000000000..17b161251 --- /dev/null +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -0,0 +1,11 @@ +layout (location = 0) in vec3 aPos; + +varying vec2 vUv; + +void main() { + + vUv = uv; + + gl_Position = vec4( aPos, 1.0 ); + +} \ No newline at end of file diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index b31c082e5..69c3dada4 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -33,13 +33,13 @@ const params = { metadata: 0, supportBlock: false, entity: '', - removeEntity () { + removeEntity() { this.entity = '' }, entityRotate: false, camera: '', - playSound () { }, - blockIsomorphicRenderBundle () { } + playSound() { }, + blockIsomorphicRenderBundle() { } } const qs = new URLSearchParams(window.location.search) @@ -59,7 +59,7 @@ const setQs = () => { let ignoreResize = false -async function main () { +async function main() { let continuousRender = false const { version } = params @@ -126,20 +126,96 @@ async function main () { const worldView = new WorldDataEmitter(world, viewDistance, targetPos) - // 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) - document.body.appendChild(renderer.domElement) + const canvas = document.createElement('canvas') + const gl = canvas.getContext('webgl2')! + + const program = createProgram(gl, ` #version 300 es + precision highp float; + layout (location = 0) in vec3 aPos; + void main() + { + gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f); + } + `, `#version 300 es + precision highp float; + out vec4 FragColor; + void main() + { + FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); + } + + + `) + + let vertices = new Float32Array([ + 0.5, 0.5, 0.0, // top right + 0.5, -0.5, 0.0, // bottom right + -0.5, -0.5, 0.0, // bottom left + -0.5, 0.5, 0.0 // top left + ]) + let indices = new Uint8Array([ // note that we start from 0! + 0, 1, 3, // first Triangle + 1, 2, 3 // second Triangle + ]) + let VBO, VAO, EBO + VAO = gl.createVertexArray(); + VBO = gl.createBuffer(); + EBO = gl.createBuffer(); + + gl.bindVertexArray(VAO); + gl.bindBuffer(gl.ARRAY_BUFFER, VBO) + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) + + new THREE.BufferAttribute(vertices, 3) + gl.vertexAttribPointer(0,3,gl.FLOAT, false, 0 , 0) + gl.enableVertexAttribArray(0) + + + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindVertexArray(null) + + //gl.attachShader(program, program) + + //gl.clearColor(0, 0, 0, 1) + //gl.clear(gl.COLOR_BUFFER_BIT) + document.body.appendChild(canvas) + + //gl.createVertexArray + + //const model = + //gl. + //gl.texImage2D() + // loop + const loop = () => { + gl.canvas.width = window.innerWidth + gl.canvas.height = window.innerHeight + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) + + + gl.clear(gl.COLOR_BUFFER_BIT) + gl.clearColor(0.5, 0, 0, 0); + gl.useProgram(program) + gl.bindVertexArray(VAO) + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); + + + requestAnimationFrame(loop) + //gl.Swa + } + loop() + + // gl.deleteVertexArray(VAO); + // gl.deleteBuffer(VBO) + // gl.deleteBuffer(EBO) + // gl.deleteProgram(program) + + return // Create viewer const viewer = new Viewer(renderer, 1) - viewer.entities.setDebugMode('basic') - viewer.setVersion(version) - viewer.entities.onSkinUpdate = () => { - viewer.update() - viewer.render() - } viewer.listen(worldView) // Load chunks @@ -147,115 +223,115 @@ async function main () { window['worldView'] = worldView window['viewer'] = viewer - params.blockIsomorphicRenderBundle = () => { - const canvas = renderer.domElement - const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one') - const sizeRaw = prompt('Size', '512') - if (!sizeRaw) return - const size = parseInt(sizeRaw) - // const size = 512 - - ignoreResize = true - canvas.width = size - canvas.height = size - renderer.setSize(size, size) - - //@ts-ignore - viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10) - viewer.scene.background = null - - const rad = THREE.MathUtils.degToRad(-120) - viewer.directionalLight.position.set( - Math.cos(rad), - Math.sin(rad), - 0.2 - ).normalize() - viewer.directionalLight.intensity = 1 - - const cameraPos = targetPos.offset(2, 2, 2) - const pitch = THREE.MathUtils.degToRad(-30) - const yaw = THREE.MathUtils.degToRad(45) - viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX') - // viewer.camera.lookAt(center.x + 0.5, center.y + 0.5, center.z + 0.5) - viewer.camera.position.set(cameraPos.x + 1, cameraPos.y + 0.5, cameraPos.z + 1) - - const allBlocks = mcData.blocksArray.map(b => b.name) - // const allBlocks = ['stone', 'warped_slab'] - - let blockCount = 1 - let blockName = allBlocks[0] - - const updateBlock = () => { - - //@ts-ignore - // viewer.setBlockStateId(targetPos, mcData.blocksByName[blockName].minStateId) - params.block = blockName - // todo cleanup (introduce getDefaultState) - onUpdate.block() - applyChanges(false, true) - } - viewer.waitForChunksToRender().then(async () => { - // wait for next macro task - await new Promise(resolve => { - setTimeout(resolve, 0) - }) - if (onlyCurrent) { - viewer.render() - onWorldUpdate() - } else { - // will be called on every render update - viewer.world.renderUpdateEmitter.addListener('update', onWorldUpdate) - updateBlock() - } - }) + // params.blockIsomorphicRenderBundle = () => { + // const canvas = renderer.domElement + // const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one') + // const sizeRaw = prompt('Size', '512') + // if (!sizeRaw) return + // const size = parseInt(sizeRaw) + // // const size = 512 + + // ignoreResize = true + // canvas.width = size + // canvas.height = size + // renderer.setSize(size, size) + + // //@ts-ignore + // viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10) + // viewer.scene.background = null + + // const rad = THREE.MathUtils.degToRad(-120) + // viewer.directionalLight.position.set( + // Math.cos(rad), + // Math.sin(rad), + // 0.2 + // ).normalize() + // viewer.directionalLight.intensity = 1 + + // const cameraPos = targetPos.offset(2, 2, 2) + // const pitch = THREE.MathUtils.degToRad(-30) + // const yaw = THREE.MathUtils.degToRad(45) + // viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX') + // // viewer.camera.lookAt(center.x + 0.5, center.y + 0.5, center.z + 0.5) + // viewer.camera.position.set(cameraPos.x + 1, cameraPos.y + 0.5, cameraPos.z + 1) + + // const allBlocks = mcData.blocksArray.map(b => b.name) + // // const allBlocks = ['stone', 'warped_slab'] + + // let blockCount = 1 + // let blockName = allBlocks[0] + + // const updateBlock = () => { + + // //@ts-ignore + // // viewer.setBlockStateId(targetPos, mcData.blocksByName[blockName].minStateId) + // params.block = blockName + // // todo cleanup (introduce getDefaultState) + // onUpdate.block() + // applyChanges(false, true) + // } + // viewer.waitForChunksToRender().then(async () => { + // // wait for next macro task + // await new Promise(resolve => { + // setTimeout(resolve, 0) + // }) + // if (onlyCurrent) { + // viewer.render() + // onWorldUpdate() + // } else { + // // will be called on every render update + // viewer.world.renderUpdateEmitter.addListener('update', onWorldUpdate) + // updateBlock() + // } + // }) - const zip = new JSZip() - zip.file('description.txt', 'Generated with prismarine-viewer') + // const zip = new JSZip() + // zip.file('description.txt', 'Generated with prismarine-viewer') - const end = async () => { - // download zip file + // const end = async () => { + // // download zip file - const a = document.createElement('a') - const blob = await zip.generateAsync({ type: 'blob' }) - const dataUrlZip = URL.createObjectURL(blob) - a.href = dataUrlZip - a.download = 'blocks_render.zip' - a.click() - URL.revokeObjectURL(dataUrlZip) - console.log('end') + // const a = document.createElement('a') + // const blob = await zip.generateAsync({ type: 'blob' }) + // const dataUrlZip = URL.createObjectURL(blob) + // a.href = dataUrlZip + // a.download = 'blocks_render.zip' + // a.click() + // URL.revokeObjectURL(dataUrlZip) + // console.log('end') - viewer.world.renderUpdateEmitter.removeListener('update', onWorldUpdate) - } + // viewer.world.renderUpdateEmitter.removeListener('update', onWorldUpdate) + // } - async function onWorldUpdate () { - // await new Promise(resolve => { - // setTimeout(resolve, 50) - // }) - const dataUrl = canvas.toDataURL('image/png') + // async function onWorldUpdate () { + // // await new Promise(resolve => { + // // setTimeout(resolve, 50) + // // }) + // const dataUrl = canvas.toDataURL('image/png') - zip.file(`${blockName}.png`, dataUrl.split(',')[1], { base64: true }) + // zip.file(`${blockName}.png`, dataUrl.split(',')[1], { base64: true }) - if (onlyCurrent) { - end() - } else { - nextBlock() - } - } - const nextBlock = async () => { - blockName = allBlocks[blockCount++] - console.log(allBlocks.length, '/', blockCount, blockName) - if (blockCount % 5 === 0) { - await new Promise(resolve => { - setTimeout(resolve, 100) - }) - } - if (blockName) { - updateBlock() - } else { - end() - } - } - } + // if (onlyCurrent) { + // end() + // } else { + // nextBlock() + // } + // } + // const nextBlock = async () => { + // blockName = allBlocks[blockCount++] + // console.log(allBlocks.length, '/', blockCount, blockName) + // if (blockCount % 5 === 0) { + // await new Promise(resolve => { + // setTimeout(resolve, 100) + // }) + // } + // if (blockName) { + // updateBlock() + // } else { + // end() + // } + // } + // } // const jsonData = await fetch('https://bluecolored.de/bluemap/maps/overworld/tiles/0/x-2/2/z1/6.json?584662').then(r => r.json()) @@ -315,7 +391,7 @@ async function main () { id: 'id', name: params.entity, pos: targetPos.offset(0.5, 1, 0.5), width: 1, height: 1, username: localStorage.testUsername, yaw: Math.PI, pitch: 0 }) const enableSkeletonDebug = (obj) => { - const {children, isSkeletonHelper} = obj + const { children, isSkeletonHelper } = obj if (!Array.isArray(children)) return if (isSkeletonHelper) { obj.visible = true @@ -333,7 +409,7 @@ async function main () { } const onUpdate = { - block () { + block() { metadataFolder.destroy() const block = mcData.blocksByName[params.block] if (!block) return @@ -376,7 +452,7 @@ async function main () { } metadataFolder.open() }, - entity () { + entity() { continuousRender = params.entity === 'player' entityUpdateShared() if (!params.entity) return @@ -396,7 +472,7 @@ async function main () { // entityRotationFolder.add(params, 'entityRotate') // entityRotationFolder.open() }, - supportBlock () { + supportBlock() { viewer.setBlockStateId(targetPos.offset(0, -1, 0), params.supportBlock ? 1 : 0) } } @@ -515,3 +591,31 @@ async function main () { }, { capture: true }) } main() + +export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmentShader: string) => { + const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => { + const shaderName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment' + const shader = gl.createShader(type)! + gl.shaderSource(shader, source) + gl.compileShader(shader) + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) + if (!success) { + const info = gl.getShaderInfoLog(shader) + gl.deleteShader(shader) + throw new Error(`Shader ${shaderName} compile error: ` + info) + } + return shader + } + + const program = gl.createProgram()! + gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) + gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)!) + gl.linkProgram(program) + const linkSuccess = gl.getProgramParameter(program, gl.LINK_STATUS) + if (!linkSuccess) { + const info = gl.getProgramInfoLog(program) + gl.deleteProgram(program) + throw new Error('Program link error: ' + info) + } + return program +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 02888293a..d4c4f066f 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,7 +7,9 @@ import { Primitives } from './primitives' import { getVersion } from './version' import EventEmitter from 'events' import { EffectComposer, RenderPass, ShaderPass, FXAAShader } from 'three-stdlib' +import { WorldHolder } from './worldrenderer' +THREE.ShaderChunk export class Viewer { scene: THREE.Scene ambientLight: THREE.AmbientLight @@ -28,17 +30,10 @@ export class Viewer { fxaaPass: ShaderPass renderPass: RenderPass - constructor(public renderer: THREE.WebGLRenderer, numWorkers?: number, public enableFXAA = false) { - this.scene = new THREE.Scene() - this.resetScene() - if (this.enableFXAA) { - this.enableFxaaScene() - } - this.world = new WorldRenderer(this.scene, numWorkers) - this.entities = new Entities(this.scene) - this.primitives = new Primitives(this.scene, this.camera) - - this.domElement = renderer.domElement + constructor(public holder: WorldHolder, numWorkers?: number, public enableFXAA = false) { + this.world = new WorldRenderer(holder, numWorkers) + // this.entities = new Entities(this.scene) + // this.primitives = new Primitives(this.scene, this.camera) } resetScene () { @@ -187,15 +182,15 @@ export class Viewer { tweenJs.update() } - render () { - if (this.composer) { - this.renderPass.camera = this.camera - this.composer.render() - } else { - this.renderer.render(this.scene, this.camera) - } - this.entities.render() - } + // render () { + // if (this.composer) { + // this.renderPass.camera = this.camera + // this.composer.render() + // } else { + // this.renderer.render(this.scene, this.camera) + // } + // this.entities.render() + // } async waitForChunksToRender () { await this.world.waitForChunksToRender() diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index bc8f55f9a..977fdfad1 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -15,6 +15,23 @@ function mod (x, n) { return ((x % n) + n) % n } +export type WorldHolder = { + add(opt: { + geometry: { + positions: Float32Array, + normals: Float32Array, + colors: Float32Array, + uvs: Float32Array, + indices: Uint32Array, + sx: number, + sy: number, + sz: number, + signs: Record + } + }) + remove(opt: { key: string }) +} + export class WorldRenderer { worldConfig = { minY: 0, worldHeight: 256 } material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) @@ -43,7 +60,7 @@ export class WorldRenderer { promisesQueue = [] as Promise[] - constructor(public scene: THREE.Scene, numWorkers = 4) { + constructor(public holder: WorldHolder, numWorkers = 4) { // init workers for (let i = 0; i < numWorkers; i++) { // Node environment needs an absolute path, but browser needs the url of the file @@ -58,12 +75,13 @@ export class WorldRenderer { setTimeout(resolve, 0) }) if (data.type === 'geometry') { - let object: THREE.Object3D = this.sectionObjects[data.key] - if (object) { - this.scene.remove(object) - dispose3(object) - delete this.sectionObjects[data.key] - } + // let object: THREE.Object3D = this.sectionObjects[data.key] + // if (object) { + // this.scene.remove(object) + // dispose3(object) + // delete this.sectionObjects[data.key] + // } + // if const chunkCoords = data.key.split(',') if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return @@ -82,39 +100,41 @@ export class WorldRenderer { // } // } - const geometry = new THREE.BufferGeometry() - geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) - geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3)) - geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3)) - geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2)) - geometry.setIndex(data.geometry.indices) - - const mesh = new THREE.Mesh(geometry, this.material) - mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) - mesh.name = 'mesh' - object = new THREE.Group() - object.add(mesh) - const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) - boxHelper.name = 'helper' - object.add(boxHelper) - object.name = 'chunk' - if (!this.showChunkBorders) { - boxHelper.visible = false - } - // should not compute it once - if (Object.keys(data.geometry.signs).length) { - for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { - const [x, y, z] = posKey.split(',') - const signBlockEntity = this.blockEntities[posKey] - if (!signBlockEntity) continue - const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity)); - if (!sign) continue - object.add(sign) - } - } - this.sectionObjects[data.key] = object - this.updatePosDataChunk(data.key) - this.scene.add(object) + return + + // const geometry = new THREE.BufferGeometry() + // geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) + // geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3)) + // geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3)) + // geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2)) + // geometry.setIndex(data.geometry.indices) + + // const mesh = new THREE.Mesh(geometry, this.material) + // mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) + // mesh.name = 'mesh' + // object = new THREE.Group() + // object.add(mesh) + // const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) + // boxHelper.name = 'helper' + // object.add(boxHelper) + // object.name = 'chunk' + // if (!this.showChunkBorders) { + // boxHelper.visible = false + // } + // // should not compute it once + // if (Object.keys(data.geometry.signs).length) { + // for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { + // const [x, y, z] = posKey.split(',') + // const signBlockEntity = this.blockEntities[posKey] + // if (!signBlockEntity) continue + // const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity)); + // if (!sign) continue + // object.add(sign) + // } + // } + // this.sectionObjects[data.key] = object + // this.updatePosDataChunk(data.key) + // this.scene.add(object) } else if (data.type === 'sectionFinished') { this.sectionsOutstanding.delete(data.key) this.renderUpdateEmitter.emit('update') @@ -230,19 +250,19 @@ export class WorldRenderer { } setVersion (version, texturesVersion = version) { - this.version = version - this.texturesVersion = texturesVersion - this.resetWorld() - this.active = true - - const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] - for (const worker of this.workers) { - const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) - mcData.version = JSON.parse(JSON.stringify(mcData.version)) - worker.postMessage({ type: 'mcData', mcData, version: this.version }) - } + // this.version = version + // this.texturesVersion = texturesVersion + // this.resetWorld() + // this.active = true + + // const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] + // for (const worker of this.workers) { + // const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) + // mcData.version = JSON.parse(JSON.stringify(mcData.version)) + // worker.postMessage({ type: 'mcData', mcData, version: this.version }) + // } - this.updateTexturesData() + // this.updateTexturesData() } updateTexturesData () { @@ -308,22 +328,22 @@ export class WorldRenderer { } removeColumn (x, z) { - this.cleanChunkTextures(x, z) + // this.cleanChunkTextures(x, z) - delete this.loadedChunks[`${x},${z}`] - for (const worker of this.workers) { - worker.postMessage({ type: 'unloadChunk', x, z }) - } - for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { - this.setSectionDirty(new Vec3(x, y, z), false) - const key = `${x},${y},${z}` - const mesh = this.sectionObjects[key] - if (mesh) { - this.scene.remove(mesh) - dispose3(mesh) - } - delete this.sectionObjects[key] - } + // delete this.loadedChunks[`${x},${z}`] + // for (const worker of this.workers) { + // worker.postMessage({ type: 'unloadChunk', x, z }) + // } + // for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { + // this.setSectionDirty(new Vec3(x, y, z), false) + // const key = `${x},${y},${z}` + // const mesh = this.sectionObjects[key] + // if (mesh) { + // this.scene.remove(mesh) + // dispose3(mesh) + // } + // delete this.sectionObjects[key] + // } } setBlockStateId (pos, stateId) { From 1175d3fae99ff5b50f26550f1591a95467d77208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Wed, 6 Mar 2024 02:36:52 +0300 Subject: [PATCH 02/86] Working texture bindings --- prismarine-viewer/esbuild.mjs | 2 + .../examples/_FragmentShader.frag | 15 ++- prismarine-viewer/examples/_VertexShader.vert | 17 +-- prismarine-viewer/examples/playground.ts | 110 +++++++++++++----- 4 files changed, 106 insertions(+), 38 deletions(-) diff --git a/prismarine-viewer/esbuild.mjs b/prismarine-viewer/esbuild.mjs index 91b787db9..1be3ca498 100644 --- a/prismarine-viewer/esbuild.mjs +++ b/prismarine-viewer/esbuild.mjs @@ -52,6 +52,8 @@ const buildOptions = { metafile: true, loader: { '.png': 'dataurl', + '.vert': 'text', + '.frag': 'text' }, plugins: [ { diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index f99b66f7b..93477e147 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -1,7 +1,16 @@ -varying vec2 vUv; +#version 300 es +precision highp float; +out vec4 FragColor; -void main() { +in vec3 ourColor; +in vec2 TexCoord; - gl_FragColor = vec4(0.5f,5.0f,0.0f,1.0f); +// texture samplers +uniform sampler2D texture1; +uniform sampler2D texture2; +void main() +{ + // linearly interpolate between both textures (80% container, 20% awesomeface) + FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord),0.0); } \ No newline at end of file diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index 17b161251..3325f6550 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -1,11 +1,14 @@ +#version 300 es layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aColor; +layout (location = 2) in vec2 aTexCoord; -varying vec2 vUv; - -void main() { - - vUv = uv; - - gl_Position = vec4( aPos, 1.0 ); +out vec3 ourColor; +out vec2 TexCoord; +void main() +{ + gl_Position = vec4(aPos, 1.0); + ourColor = aColor; + TexCoord = vec2(aTexCoord.x, aTexCoord.y); } \ No newline at end of file diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 69c3dada4..1914e7961 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -13,6 +13,16 @@ import JSZip from 'jszip' import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' +//@ts-ignore +import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' +//@ts-ignore +import Stone from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/stone.png' + +//@ts-ignore +import VertShader from './_VertexShader.vert' +//@ts-ignore +import FragShader from './_FragmentShader.frag' + globalThis.THREE = THREE //@ts-ignore require('three/examples/js/controls/OrbitControls') @@ -129,29 +139,13 @@ async function main() { const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2')! - const program = createProgram(gl, ` #version 300 es - precision highp float; - layout (location = 0) in vec3 aPos; - void main() - { - gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f); - } - `, `#version 300 es - precision highp float; - out vec4 FragColor; - void main() - { - FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); - } - - - `) + const program = createProgram(gl,VertShader, FragShader) let vertices = new Float32Array([ - 0.5, 0.5, 0.0, // top right - 0.5, -0.5, 0.0, // bottom right - -0.5, -0.5, 0.0, // bottom left - -0.5, 0.5, 0.0 // top left + 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right + 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right + -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left + -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0 ]) let indices = new Uint8Array([ // note that we start from 0! 0, 1, 3, // first Triangle @@ -169,14 +163,65 @@ async function main() { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) - new THREE.BufferAttribute(vertices, 3) - gl.vertexAttribPointer(0,3,gl.FLOAT, false, 0 , 0) + //new THREE.BufferAttribute(vertices, 3) + + gl.vertexAttribPointer(0,3,gl.FLOAT, false, 8 * 4, 0) gl.enableVertexAttribArray(0) + gl.vertexAttribPointer(1,3,gl.FLOAT, false, 8*4, 3*4) + gl.enableVertexAttribArray(1) + + gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) + gl.enableVertexAttribArray(2) + gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindVertexArray(null) + let image = new Image(); + // simple black white chess image 10x10 + image.src = Dirt + let image2 = new Image(); + // simple black white chess image 10x10 + image2.src = Stone + + console.log(image.src) + await new Promise((resolve) => { + image.onload = resolve + }) + await new Promise((resolve) => { + image2.onload = resolve + }) + + let texture1 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture1); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + //.tset texture fgl.ering paramegl.s + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image.width, image.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image); + //gl.generateMipmap(gl.TEXTURE_2D); + + let texture2 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture2); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + //.tset texture fgl.ering paramegl.s + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image2.width, image2.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image2); + //gl.generateMipmap(gl.TEXTURE_2D); + + gl.useProgram(program) + + gl.uniform1i(gl.getUniformLocation(program, "texture1"), 0); + gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); + //gl.attachShader(program, program) //gl.clearColor(0, 0, 0, 1) @@ -194,14 +239,20 @@ async function main() { gl.canvas.height = window.innerHeight gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) - + gl.clearColor(0.1, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT) - gl.clearColor(0.5, 0, 0, 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture1); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, texture2); + + gl.useProgram(program) gl.bindVertexArray(VAO) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); - + requestAnimationFrame(loop) //gl.Swa } @@ -215,7 +266,7 @@ async function main() { return // Create viewer - const viewer = new Viewer(renderer, 1) + const viewer = new Viewer(null as any | null, 1) as any viewer.listen(worldView) // Load chunks @@ -575,7 +626,7 @@ async function main() { const { camera } = viewer viewer.camera.aspect = window.innerWidth / window.innerHeight viewer.camera.updateProjectionMatrix() - renderer.setSize(window.innerWidth, window.innerHeight) + // renderer.setSize(window.innerWidth, window.innerHeight) animate() } @@ -598,6 +649,7 @@ export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, const shader = gl.createShader(type)! gl.shaderSource(shader, source) gl.compileShader(shader) + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) if (!success) { const info = gl.getShaderInfoLog(shader) @@ -607,6 +659,8 @@ export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, return shader } + + const program = gl.createProgram()! gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)!) From 06b2f3bfa87a9b8d7f4171a24dc34994a864ad21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Wed, 6 Mar 2024 03:26:03 +0300 Subject: [PATCH 03/86] degrees_to_radians --- package.json | 3 +- prismarine-viewer/examples/playground.ts | 43 ++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index f9cfe4627..cfbd6955e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0-dev", "description": "A minecraft client running in a browser", "scripts": { - "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --watch", + "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node 4 --watch", "start-watch-script": "nodemon -w esbuild.mjs --watch", "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod", "check-build": "tsc && pnpm test-unit && pnpm build", @@ -52,6 +52,7 @@ "jszip": "^3.10.1", "lit": "^2.8.0", "lodash-es": "^4.17.21", + "math.gl": "^4.0.0", "minecraft-assets": "^1.12.2", "minecraft-data": "3.61.0", "net-browserify": "github:zardoy/prismarinejs-net-browserify", diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 1914e7961..dc058ceb3 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -12,6 +12,7 @@ import { loadScript } from '../viewer/lib/utils' import JSZip from 'jszip' import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' +import * as Mathgl from 'math.gl' //@ts-ignore import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' @@ -22,6 +23,8 @@ import Stone from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/stone.pn import VertShader from './_VertexShader.vert' //@ts-ignore import FragShader from './_FragmentShader.frag' +import { WebGLUtils } from 'three/src/renderers/webgl/WebGLUtils' +import { transform } from 'esbuild' globalThis.THREE = THREE //@ts-ignore @@ -142,10 +145,10 @@ async function main() { const program = createProgram(gl,VertShader, FragShader) let vertices = new Float32Array([ - 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right - 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right - -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left - -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0 + 0.5, 0.5, 0.0, 1.0, 1.0, // top right + 0.5, -0.5, 0.0, 1.0, 0.0, // bottom right + -0.5, -0.5, 0.0, 0.0, 0.0, // bottom left + -0.5, 0.5, 0.0, 0.0, 1.0 ]) let indices = new Uint8Array([ // note that we start from 0! 0, 1, 3, // first Triangle @@ -165,14 +168,14 @@ async function main() { //new THREE.BufferAttribute(vertices, 3) - gl.vertexAttribPointer(0,3,gl.FLOAT, false, 8 * 4, 0) + gl.vertexAttribPointer(0,3,gl.FLOAT, false, 5 * 4, 0) gl.enableVertexAttribArray(0) - gl.vertexAttribPointer(1,3,gl.FLOAT, false, 8*4, 3*4) + gl.vertexAttribPointer(1,2,gl.FLOAT, false, 5*4, 3*4) gl.enableVertexAttribArray(1) - gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) - gl.enableVertexAttribArray(2) + //gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) + //gl.enableVertexAttribArray(2) gl.bindBuffer(gl.ARRAY_BUFFER, null); @@ -228,13 +231,16 @@ async function main() { //gl.clear(gl.COLOR_BUFFER_BIT) document.body.appendChild(canvas) + //console.log('webglUtils', webglUtils) + + //WebGLUtils. //gl.createVertexArray //const model = //gl. //gl.texImage2D() // loop - const loop = () => { + const loop = (performance) => { gl.canvas.width = window.innerWidth gl.canvas.height = window.innerHeight gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) @@ -246,8 +252,17 @@ async function main() { gl.bindTexture(gl.TEXTURE_2D, texture1); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, texture2); - + let transform = Mathgl.Matrix4.IDENTITY + //transform = transform.translate([0.2,-0.5,0.0]) + //transform = transform.rotateXYZ([0,3,0]) + transform = transform.rotateAxis(0, [0,0,1]) + // glm::mat4 transform = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first + //transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f)); + //transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f)); + console.log(transform) + gl.uniformMatrix4fv(gl.getUniformLocation(program, "transform"), false, transform); + // Mathgl gl.useProgram(program) gl.bindVertexArray(VAO) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); @@ -256,7 +271,7 @@ async function main() { requestAnimationFrame(loop) //gl.Swa } - loop() + loop(performance.now) // gl.deleteVertexArray(VAO); // gl.deleteBuffer(VBO) @@ -274,6 +289,12 @@ async function main() { window['worldView'] = worldView window['viewer'] = viewer + function degrees_to_radians(degrees) +{ + var pi = Math.PI; + return degrees * (pi/180); +} + // params.blockIsomorphicRenderBundle = () => { // const canvas = renderer.domElement // const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one') From b28f69c12677433608b9b1c7b62abca2b9ab24ec Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 18 Mar 2024 21:42:42 +0300 Subject: [PATCH 04/86] nice work by ilya --- package.json | 1 + pnpm-lock.yaml | 26 ++++++ prismarine-viewer/examples/_VertexShader.vert | 9 +- prismarine-viewer/examples/playground.ts | 84 ++++++++++--------- 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index cfbd6955e..0900795c7 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "stats.js": "^0.17.0", "tabbable": "^6.2.0", "title-case": "3.x", + "twgl.js": "^5.5.4", "ua-parser-js": "^1.0.37", "valtio": "^1.11.1", "workbox-build": "^7.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6df7d660b..0e3d8e6f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + math.gl: + specifier: ^4.0.0 + version: 4.0.1 minecraft-assets: specifier: ^1.12.2 version: 1.12.2 @@ -141,6 +144,9 @@ importers: title-case: specifier: 3.x version: 3.0.3 + twgl.js: + specifier: ^5.5.4 + version: 5.5.4 ua-parser-js: specifier: ^1.0.37 version: 1.0.37 @@ -3187,6 +3193,16 @@ packages: - supports-color dev: false + /@math.gl/core@4.0.1: + resolution: {integrity: sha512-9IewNjR9V66o+gYIIq5agFoHy6ZT6DRpRGQBfsUpZz4glAqOjVt64he8GGzjpmqfT+kKT4qwQ7nQl/hZLF15qA==} + dependencies: + '@math.gl/types': 4.0.1 + dev: false + + /@math.gl/types@4.0.1: + resolution: {integrity: sha512-E9qBKAjVBiZD8Is7TbygiLGtYBP3GSLus6RUJSuzFQegdYXeVagvrs4UkBJxhrRAxw4crfH0Tq7IhTMKuuJNQw==} + dev: false + /@mdx-js/react@2.3.0(react@18.2.0): resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: @@ -10745,6 +10761,12 @@ packages: react: 18.2.0 dev: true + /math.gl@4.0.1: + resolution: {integrity: sha512-Yvw1HfmsDePxwhCBvGT8teyPN0mwxcxUaWLoDaRuZYxoUYa9HRg+6ywBS+yXopC0wIS9MFi+BCVKP8hdJpaJjw==} + dependencies: + '@math.gl/core': 4.0.1 + dev: false + /md5-file@4.0.0: resolution: {integrity: sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==} engines: {node: '>=6.0'} @@ -14158,6 +14180,10 @@ packages: /tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + /twgl.js@5.5.4: + resolution: {integrity: sha512-6kFOmijOpmblTN9CCwOTCxK4lPg7rCyQjLuub6EMOlEp89Ex6yUcsMjsmH7andNPL2NE3XmHdqHeP5gVKKPhxw==} + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index 3325f6550..cc9d30837 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -6,9 +6,14 @@ layout (location = 2) in vec2 aTexCoord; out vec3 ourColor; out vec2 TexCoord; +//uniform mat4 transform; +uniform mat4 projection; +uniform mat4 model; +uniform mat4 view; + void main() { - gl_Position = vec4(aPos, 1.0); + gl_Position = projection * view * model * vec4(aPos, 1.0); ourColor = aColor; TexCoord = vec2(aTexCoord.x, aTexCoord.y); -} \ No newline at end of file +} diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index dc058ceb3..713abd3a8 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -12,7 +12,8 @@ import { loadScript } from '../viewer/lib/utils' import JSZip from 'jszip' import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' -import * as Mathgl from 'math.gl' +// import * as Mathgl from 'math.gl' +import { m4 } from 'twgl.js' //@ts-ignore import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' @@ -23,8 +24,6 @@ import Stone from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/stone.pn import VertShader from './_VertexShader.vert' //@ts-ignore import FragShader from './_FragmentShader.frag' -import { WebGLUtils } from 'three/src/renderers/webgl/WebGLUtils' -import { transform } from 'esbuild' globalThis.THREE = THREE //@ts-ignore @@ -142,13 +141,14 @@ async function main() { const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2')! - const program = createProgram(gl,VertShader, FragShader) + const program = createProgram(gl,VertShader, FragShader) + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) let vertices = new Float32Array([ - 0.5, 0.5, 0.0, 1.0, 1.0, // top right - 0.5, -0.5, 0.0, 1.0, 0.0, // bottom right - -0.5, -0.5, 0.0, 0.0, 0.0, // bottom left - -0.5, 0.5, 0.0, 0.0, 1.0 + 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right + 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right + -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left + -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0 ]) let indices = new Uint8Array([ // note that we start from 0! 0, 1, 3, // first Triangle @@ -168,14 +168,14 @@ async function main() { //new THREE.BufferAttribute(vertices, 3) - gl.vertexAttribPointer(0,3,gl.FLOAT, false, 5 * 4, 0) + gl.vertexAttribPointer(0,3,gl.FLOAT, false, 8 * 4, 0) gl.enableVertexAttribArray(0) - gl.vertexAttribPointer(1,2,gl.FLOAT, false, 5*4, 3*4) + gl.vertexAttribPointer(1,3,gl.FLOAT, false, 8*4, 3*4) gl.enableVertexAttribArray(1) - //gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) - //gl.enableVertexAttribArray(2) + gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) + gl.enableVertexAttribArray(2) gl.bindBuffer(gl.ARRAY_BUFFER, null); @@ -195,9 +195,9 @@ async function main() { await new Promise((resolve) => { image2.onload = resolve }) - - let texture1 = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture1); + + let texture1 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture1); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -208,8 +208,8 @@ async function main() { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image.width, image.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image); //gl.generateMipmap(gl.TEXTURE_2D); - let texture2 = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture2); + let texture2 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture2); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -231,15 +231,6 @@ async function main() { //gl.clear(gl.COLOR_BUFFER_BIT) document.body.appendChild(canvas) - //console.log('webglUtils', webglUtils) - - //WebGLUtils. - //gl.createVertexArray - - //const model = - //gl. - //gl.texImage2D() - // loop const loop = (performance) => { gl.canvas.width = window.innerWidth gl.canvas.height = window.innerHeight @@ -252,22 +243,32 @@ async function main() { gl.bindTexture(gl.TEXTURE_2D, texture1); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, texture2); - let transform = Mathgl.Matrix4.IDENTITY - //transform = transform.translate([0.2,-0.5,0.0]) - - //transform = transform.rotateXYZ([0,3,0]) - transform = transform.rotateAxis(0, [0,0,1]) - // glm::mat4 transform = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first - //transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f)); + + const view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) + const projection = m4.perspective(45 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 100) + const model = m4.identity() + m4.rotateX(model, performance / 1000, model); + m4.rotateY(model, performance / 2500, model) + m4.translate(view, [0, 0, -10], view) + + + + //let transform = m4.identity() + // transform = m4.translate(transform, [0.5, 0.5, 0.0], transform) + //m4.axisRotate(transform, [0,1.0,0.0], performance/100, transform) + + // glm::mat4 transform = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first + //transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f)); //transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f)); - console.log(transform) - gl.uniformMatrix4fv(gl.getUniformLocation(program, "transform"), false, transform); - // Mathgl + //gl.uniformMatrix4fv(gl.getUniformLocation(program, "transform"), false, transform); + gl.uniformMatrix4fv(gl.getUniformLocation(program, "projection"), false, projection); + gl.uniformMatrix4fv(gl.getUniformLocation(program, "model"), false, model); + gl.uniformMatrix4fv(gl.getUniformLocation(program, "view"), false, view); gl.useProgram(program) gl.bindVertexArray(VAO) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); - + requestAnimationFrame(loop) //gl.Swa } @@ -281,7 +282,7 @@ async function main() { return // Create viewer - const viewer = new Viewer(null as any | null, 1) as any + const viewer = new Viewer(null as any | null, 1) viewer.listen(worldView) // Load chunks @@ -440,6 +441,7 @@ async function main() { //@ts-ignore const controls = new globalThis.THREE.OrbitControls(viewer.camera, renderer.domElement) + viewer.camer controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5) const cameraPos = targetPos.offset(2, 2, 2) @@ -670,7 +672,7 @@ export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, const shader = gl.createShader(type)! gl.shaderSource(shader, source) gl.compileShader(shader) - + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) if (!success) { const info = gl.getShaderInfoLog(shader) @@ -680,7 +682,7 @@ export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, return shader } - + const program = gl.createProgram()! gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) @@ -693,4 +695,4 @@ export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, throw new Error('Program link error: ' + info) } return program -} \ No newline at end of file +} From 6e84db0a7ad0770fb2e3bf542b0f4f369ae69dad Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 21 Mar 2024 02:56:32 +0300 Subject: [PATCH 05/86] make cubes undo viewer changes before merge --- .../examples/_FragmentShader.frag | 14 +- prismarine-viewer/examples/_VertexShader.vert | 13 +- prismarine-viewer/examples/playground.ts | 568 ++++++------------ prismarine-viewer/viewer/lib/worldrenderer.ts | 32 +- 4 files changed, 212 insertions(+), 415 deletions(-) diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index 93477e147..0a0bfa7fe 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -2,15 +2,19 @@ precision highp float; out vec4 FragColor; -in vec3 ourColor; in vec2 TexCoord; -// texture samplers uniform sampler2D texture1; uniform sampler2D texture2; +uniform vec2 uv; +uniform vec2 suv; + void main() { - // linearly interpolate between both textures (80% container, 20% awesomeface) - FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord),0.0); -} \ No newline at end of file + vec2 position = vec2(1, 1); // I assume gets tile at (1, 1) since the size of the tiles are 1/16 + vec2 size = vec2(1/64, 1/64); + vec2 coord = uv + TexCoord * (1.0f/64.0f); + + FragColor = mix(texture(texture1, coord), texture(texture2, coord), 1.0); +} diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index cc9d30837..ce5fee718 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -1,19 +1,16 @@ #version 300 es layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aColor; -layout (location = 2) in vec2 aTexCoord; +layout (location = 1) in vec2 aTexCoord; -out vec3 ourColor; out vec2 TexCoord; -//uniform mat4 transform; -uniform mat4 projection; uniform mat4 model; uniform mat4 view; +uniform mat4 projection; +//uniform vec4 uv; void main() { - gl_Position = projection * view * model * vec4(aPos, 1.0); - ourColor = aColor; - TexCoord = vec2(aTexCoord.x, aTexCoord.y); + gl_Position = projection * view * model * vec4(aPos, 1.0f); + TexCoord = vec2(aTexCoord.x, (1.0 - aTexCoord.y)); } diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 713abd3a8..693c4eefc 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -14,6 +14,7 @@ import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' // import * as Mathgl from 'math.gl' import { m4 } from 'twgl.js' +import Stats from 'stats.js' //@ts-ignore import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' @@ -94,7 +95,8 @@ async function main() { const mcData = require('minecraft-data')(version) window['loadedData'] = mcData - gui.add(params, 'version', globalThis.includedVersions) +const stats = new Stats() +gui.add(params, 'version', globalThis.includedVersions) gui.add(params, 'block', mcData.blocksArray.map(b => b.name).sort((a, b) => a.localeCompare(b))) const metadataGui = gui.add(params, 'metadata') gui.add(params, 'supportBlock') @@ -141,41 +143,82 @@ async function main() { const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2')! - const program = createProgram(gl,VertShader, FragShader) - const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) + const program = createProgram(gl, VertShader, FragShader) + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000) let vertices = new Float32Array([ - 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // top right - 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, // bottom right - -0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, // bottom left - -0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0 + -0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 1.0, 0.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + -0.5, 0.5, -0.5, 0.0, 1.0, + -0.5, -0.5, -0.5, 0.0, 0.0, + + -0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 1.0, + 0.5, 0.5, 0.5, 1.0, 1.0, + -0.5, 0.5, 0.5, 0.0, 1.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + + -0.5, 0.5, 0.5, 1.0, 0.0, + -0.5, 0.5, -0.5, 1.0, 1.0, + -0.5, -0.5, -0.5, 0.0, 1.0, + -0.5, -0.5, -0.5, 0.0, 1.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, 0.5, 1.0, 0.0, + + 0.5, 0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + 0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, + + -0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, -0.5, 1.0, 1.0, + 0.5, -0.5, 0.5, 1.0, 0.0, + 0.5, -0.5, 0.5, 1.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 1.0, + + -0.5, 0.5, -0.5, 0.0, 1.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + 0.5, 0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 1.0 ]) - let indices = new Uint8Array([ // note that we start from 0! - 0, 1, 3, // first Triangle - 1, 2, 3 // second Triangle - ]) - let VBO, VAO, EBO - VAO = gl.createVertexArray(); + + let CubePositions = [] as any + + //write random coordinates to cube positions xyz ten cubes; + for (let i = 0; i < 100_000; i++) { + let x = Math.random() * 100 - 50; + let y = Math.random() * 100 - 50; + let z = Math.random() * 100 - 100; + CubePositions.push([x, y, z]); + } + + let VBO, VAO = gl.createVertexArray(); VBO = gl.createBuffer(); - EBO = gl.createBuffer(); + //EBO = gl.createBuffer(); gl.bindVertexArray(VAO); gl.bindBuffer(gl.ARRAY_BUFFER, VBO) gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) - - //new THREE.BufferAttribute(vertices, 3) + //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) + //gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) - gl.vertexAttribPointer(0,3,gl.FLOAT, false, 8 * 4, 0) + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) gl.enableVertexAttribArray(0) - gl.vertexAttribPointer(1,3,gl.FLOAT, false, 8*4, 3*4) + gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) gl.enableVertexAttribArray(1) - gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) - gl.enableVertexAttribArray(2) + //gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) + //gl.enableVertexAttribArray(2) gl.bindBuffer(gl.ARRAY_BUFFER, null); @@ -186,7 +229,7 @@ async function main() { image.src = Dirt let image2 = new Image(); // simple black white chess image 10x10 - image2.src = Stone + image2.src = '/textures/1.18.1.png' console.log(image.src) await new Promise((resolve) => { @@ -196,6 +239,55 @@ async function main() { image2.onload = resolve }) + let pitch = 0, yaw = 0; + let x = 0, y = 0, z = 0; + + const keys = (e) => { + const code = e.code + const pressed = e.type === 'keydown' + if (pressed) { + if (code === 'KeyW') { + z--; + } + if (code === 'KeyS') { + z++; + } + if (code === 'KeyA') { + x--; + } + if (code === 'KeyD') { + x++; + } + } + } + window.addEventListener('keydown', keys) + window.addEventListener('keyup', keys) + + // mouse + const mouse = { x: 0, y: 0 } + const mouseMove = (e) => { + if (e.buttons === 1) { + yaw += e.movementY/20; + pitch += e.movementX/20; + } + } + window.addEventListener('mousemove', mouseMove) + + const viewer = new Viewer(null as any | null, 1) + globalThis.viewer = viewer + viewer.world.texturesVersion = ('1.18.1') + viewer.world.updateTexturesData() + await new Promise(resolve => { + // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) + // viewer.world.material.map!.image.onload = () => { + // console.log(this.material.map!.image) + // resolve() + // } + viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) + }) + console.log(viewer.world.downloadedBlockStatesData) + const names = Object.keys(viewer.world.downloadedBlockStatesData) + let texture1 = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture1); @@ -226,51 +318,93 @@ async function main() { gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); //gl.attachShader(program, program) - + gl.enable(gl.DEPTH_TEST) + //gl.generateMipmap() + //gl.enable(gl) //gl.clearColor(0, 0, 0, 1) //gl.clear(gl.COLOR_BUFFER_BIT) document.body.appendChild(canvas) + let view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) + const projection = m4.perspective(75 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 512) + // view = m4.identity(); + // m4.rotateX(view, yaw * Math.PI / 180) + // m4.rotateY(view, pitch * Math.PI / 180) + // m4.translate(view, [x,y,z], view) + let ModelUniform = gl.getUniformLocation(program, "model") + let uvUniform = gl.getUniformLocation(program, "uv"); + let ViewUniform = gl.getUniformLocation(program, "view") + let ProjectionUniform = gl.getUniformLocation(program, "projection") + + // stats.addPanel(new Stats.Panel('FPS', '#0ff', '#002')) + document.body.appendChild(stats.dom) const loop = (performance) => { - gl.canvas.width = window.innerWidth - gl.canvas.height = window.innerHeight + stats.begin() + gl.canvas.width = window.innerWidth * window.devicePixelRatio + gl.canvas.height = window.innerHeight * window.devicePixelRatio gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) + view = m4.identity(); + m4.rotateX(view, yaw * Math.PI / 180, view) + m4.rotateY(view, pitch * Math.PI / 180, view) + m4.translate(view, [x,y,z], view) + gl.clearColor(0.1, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT) + gl.clear(gl.DEPTH_BUFFER_BIT) gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture1); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, texture2); - const view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) - const projection = m4.perspective(45 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 100) - const model = m4.identity() - m4.rotateX(model, performance / 1000, model); - m4.rotateY(model, performance / 2500, model) - m4.translate(view, [0, 0, -10], view) + gl.useProgram(program) + gl.uniformMatrix4fv(ViewUniform, false, view); + gl.uniformMatrix4fv(ProjectionUniform, false, projection); - //let transform = m4.identity() - // transform = m4.translate(transform, [0.5, 0.5, 0.0], transform) - //m4.axisRotate(transform, [0,1.0,0.0], performance/100, transform) - // glm::mat4 transform = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first - //transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f)); - //transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f)); - //gl.uniformMatrix4fv(gl.getUniformLocation(program, "transform"), false, transform); - gl.uniformMatrix4fv(gl.getUniformLocation(program, "projection"), false, projection); - gl.uniformMatrix4fv(gl.getUniformLocation(program, "model"), false, model); - gl.uniformMatrix4fv(gl.getUniformLocation(program, "view"), false, view); - gl.useProgram(program) gl.bindVertexArray(VAO) - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); + let i = 0 + CubePositions.forEach((cubePosition) => { + const model = m4.identity() + + m4.translate(model, [cubePosition[0], cubePosition[1], cubePosition[2]], model); + //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); + //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) + //m4.rotateZ(model, Math.random() / 1010, model) + gl.uniformMatrix4fv(ModelUniform, false, model); + gl.uniform2fv(uvUniform, [i%64 * 1/64,parseInt(i/64) * 1/64]); + + // let result + // i %= names.length / 2 + // for (const name of ['stone']) { + // result = viewer.world.downloadedBlockStatesData[name]?.variants?.['']?.[0]?.model?.elements?.[0]?.faces?.north?.texture + // i++ + // if (result) break + // } + // const + // const tileSize = image.width + // const blocks = + // result = { + // v: 3*1/64, + // u: 4*1/64 + // } + + i++ + i %= 800; + + + gl.drawArrays(gl.TRIANGLES, 0, 36); + }) + ///model.translate([0, 0, 0], model) + requestAnimationFrame(loop) //gl.Swa + stats.end() } loop(performance.now) @@ -282,7 +416,6 @@ async function main() { return // Create viewer - const viewer = new Viewer(null as any | null, 1) viewer.listen(worldView) // Load chunks @@ -290,121 +423,10 @@ async function main() { window['worldView'] = worldView window['viewer'] = viewer - function degrees_to_radians(degrees) -{ - var pi = Math.PI; - return degrees * (pi/180); -} - - // params.blockIsomorphicRenderBundle = () => { - // const canvas = renderer.domElement - // const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one') - // const sizeRaw = prompt('Size', '512') - // if (!sizeRaw) return - // const size = parseInt(sizeRaw) - // // const size = 512 - - // ignoreResize = true - // canvas.width = size - // canvas.height = size - // renderer.setSize(size, size) - - // //@ts-ignore - // viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10) - // viewer.scene.background = null - - // const rad = THREE.MathUtils.degToRad(-120) - // viewer.directionalLight.position.set( - // Math.cos(rad), - // Math.sin(rad), - // 0.2 - // ).normalize() - // viewer.directionalLight.intensity = 1 - - // const cameraPos = targetPos.offset(2, 2, 2) - // const pitch = THREE.MathUtils.degToRad(-30) - // const yaw = THREE.MathUtils.degToRad(45) - // viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX') - // // viewer.camera.lookAt(center.x + 0.5, center.y + 0.5, center.z + 0.5) - // viewer.camera.position.set(cameraPos.x + 1, cameraPos.y + 0.5, cameraPos.z + 1) - - // const allBlocks = mcData.blocksArray.map(b => b.name) - // // const allBlocks = ['stone', 'warped_slab'] - - // let blockCount = 1 - // let blockName = allBlocks[0] - - // const updateBlock = () => { - - // //@ts-ignore - // // viewer.setBlockStateId(targetPos, mcData.blocksByName[blockName].minStateId) - // params.block = blockName - // // todo cleanup (introduce getDefaultState) - // onUpdate.block() - // applyChanges(false, true) - // } - // viewer.waitForChunksToRender().then(async () => { - // // wait for next macro task - // await new Promise(resolve => { - // setTimeout(resolve, 0) - // }) - // if (onlyCurrent) { - // viewer.render() - // onWorldUpdate() - // } else { - // // will be called on every render update - // viewer.world.renderUpdateEmitter.addListener('update', onWorldUpdate) - // updateBlock() - // } - // }) - - // const zip = new JSZip() - // zip.file('description.txt', 'Generated with prismarine-viewer') - - // const end = async () => { - // // download zip file - - // const a = document.createElement('a') - // const blob = await zip.generateAsync({ type: 'blob' }) - // const dataUrlZip = URL.createObjectURL(blob) - // a.href = dataUrlZip - // a.download = 'blocks_render.zip' - // a.click() - // URL.revokeObjectURL(dataUrlZip) - // console.log('end') - - // viewer.world.renderUpdateEmitter.removeListener('update', onWorldUpdate) - // } - - // async function onWorldUpdate () { - // // await new Promise(resolve => { - // // setTimeout(resolve, 50) - // // }) - // const dataUrl = canvas.toDataURL('image/png') - - // zip.file(`${blockName}.png`, dataUrl.split(',')[1], { base64: true }) - - // if (onlyCurrent) { - // end() - // } else { - // nextBlock() - // } - // } - // const nextBlock = async () => { - // blockName = allBlocks[blockCount++] - // console.log(allBlocks.length, '/', blockCount, blockName) - // if (blockCount % 5 === 0) { - // await new Promise(resolve => { - // setTimeout(resolve, 100) - // }) - // } - // if (blockName) { - // updateBlock() - // } else { - // end() - // } - // } - // } + function degrees_to_radians(degrees) { + var pi = Math.PI; + return degrees * (pi / 180); + } // const jsonData = await fetch('https://bluecolored.de/bluemap/maps/overworld/tiles/0/x-2/2/z1/6.json?584662').then(r => r.json()) @@ -437,232 +459,6 @@ async function main() { // side: THREE.FrontSide, // wireframe: false // }) - - - //@ts-ignore - const controls = new globalThis.THREE.OrbitControls(viewer.camera, renderer.domElement) - viewer.camer - 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) - const yaw = THREE.MathUtils.degToRad(45) - 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) - controls.update() - - let blockProps = {} - let entityOverrides = {} - const getBlock = () => { - return mcData.blocksByName[params.block || 'air'] - } - - const entityUpdateShared = () => { - viewer.entities.clear() - if (!params.entity) return - worldView.emit('entity', { - id: 'id', name: params.entity, pos: targetPos.offset(0.5, 1, 0.5), width: 1, height: 1, username: localStorage.testUsername, yaw: Math.PI, pitch: 0 - }) - const enableSkeletonDebug = (obj) => { - const { children, isSkeletonHelper } = obj - if (!Array.isArray(children)) return - if (isSkeletonHelper) { - obj.visible = true - return - } - for (const child of children) { - if (typeof child === 'object') enableSkeletonDebug(child) - } - } - enableSkeletonDebug(viewer.entities.entities['id']) - setTimeout(() => { - viewer.update() - viewer.render() - }, TWEEN_DURATION) - } - - const onUpdate = { - block() { - metadataFolder.destroy() - const block = mcData.blocksByName[params.block] - if (!block) return - const props = new Block(block.id, 0, 0).getProperties() - //@ts-ignore - const { states } = mcData.blocksByStateId[getBlock()?.minStateId] ?? {} - metadataFolder = gui.addFolder('metadata') - if (states) { - for (const state of states) { - let defaultValue - switch (state.type) { - case 'enum': - defaultValue = state.values[0] - break - case 'bool': - defaultValue = false - break - case 'int': - defaultValue = 0 - break - case 'direction': - defaultValue = 'north' - break - - default: - continue - } - blockProps[state.name] = defaultValue - if (state.type === 'enum') { - metadataFolder.add(blockProps, state.name, state.values) - } else { - metadataFolder.add(blockProps, state.name) - } - } - } else { - for (const [name, value] of Object.entries(props)) { - blockProps[name] = value - metadataFolder.add(blockProps, name) - } - } - metadataFolder.open() - }, - entity() { - continuousRender = params.entity === 'player' - entityUpdateShared() - if (!params.entity) return - if (params.entity === 'player') { - viewer.entities.updatePlayerSkin('id', viewer.entities.entities.id.username, true, true) - viewer.entities.playAnimation('id', 'running') - } - // let prev = false - // setInterval(() => { - // viewer.entities.playAnimation('id', prev ? 'running' : 'idle') - // prev = !prev - // }, 1000) - - Entity.getStaticData(params.entity) - // entityRotationFolder.destroy() - // entityRotationFolder = gui.addFolder('entity metadata') - // entityRotationFolder.add(params, 'entityRotate') - // entityRotationFolder.open() - }, - supportBlock() { - viewer.setBlockStateId(targetPos.offset(0, -1, 0), params.supportBlock ? 1 : 0) - } - } - - - const applyChanges = (metadataUpdate = false, skipQs = false) => { - const blockId = getBlock()?.id - let block: BlockLoader.Block - if (metadataUpdate) { - block = new Block(blockId, 0, params.metadata) - Object.assign(blockProps, block.getProperties()) - for (const _child of metadataFolder.children) { - const child = _child as import('lil-gui').Controller - child.updateDisplay() - } - } else { - try { - //@ts-ignore - block = Block.fromProperties(blockId ?? -1, blockProps, 0) - } catch (err) { - console.error(err) - block = Block.fromStateId(0, 0) - } - } - - //@ts-ignore - viewer.setBlockStateId(targetPos, block.stateId) - console.log('up stateId', block.stateId) - params.metadata = block.metadata - metadataGui.updateDisplay() - if (!skipQs) { - setQs() - } - } - gui.onChange(({ property, object }) => { - if (object === params) { - if (property === 'camera') return - onUpdate[property]?.() - applyChanges(property === 'metadata') - } else { - applyChanges() - } - }) - viewer.waitForChunksToRender().then(async () => { - await new Promise(resolve => { - setTimeout(resolve, 0) - }) - for (const update of Object.values(onUpdate)) { - update() - } - applyChanges(true) - gui.openAnimated() - }) - - const animate = () => { - // if (controls) controls.update() - // worldView.updatePosition(controls.target) - viewer.update() - viewer.render() - // window.requestAnimationFrame(animate) - } - viewer.world.renderUpdateEmitter.addListener('update', () => { - animate() - }) - animate() - - // #region camera rotation param - if (params.camera) { - const [x, y] = params.camera.split(',') - viewer.camera.rotation.set(parseFloat(x), parseFloat(y), 0, 'ZYX') - controls.update() - console.log(viewer.camera.rotation.x, parseFloat(x)) - } - const throttledCamQsUpdate = _.throttle(() => { - const { camera } = viewer - // params.camera = `${camera.rotation.x.toFixed(2)},${camera.rotation.y.toFixed(2)}` - setQs() - }, 200) - controls.addEventListener('change', () => { - throttledCamQsUpdate() - animate() - }) - // #endregion - - const continuousUpdate = () => { - if (continuousRender) { - animate() - } - requestAnimationFrame(continuousUpdate) - } - continuousUpdate() - - window.onresize = () => { - if (ignoreResize) return - // const vec3 = new THREE.Vector3() - // vec3.set(-1, -1, -1).unproject(viewer.camera) - // console.log(vec3) - // box.position.set(vec3.x, vec3.y, vec3.z-1) - - const { camera } = viewer - viewer.camera.aspect = window.innerWidth / window.innerHeight - viewer.camera.updateProjectionMatrix() - // renderer.setSize(window.innerWidth, window.innerHeight) - - animate() - } - window.dispatchEvent(new Event('resize')) - - params.playSound = () => { - viewer.playSound(targetPos, 'button_click.mp3') - } - addEventListener('keydown', (e) => { - if (e.code === 'KeyE') { - params.playSound() - } - }, { capture: true }) } main() diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index 977fdfad1..4653a6249 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -101,7 +101,7 @@ export class WorldRenderer { // } return - + // const geometry = new THREE.BufferGeometry() // geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) // geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3)) @@ -274,24 +274,24 @@ export class WorldRenderer { this.material.map.onUpdate = () => { this.downloadedTextureImage = this.material.map!.image } - }) - - const loadBlockStates = async () => { - return new Promise(resolve => { - if (this.customBlockStatesData) return resolve(this.customBlockStatesData) - return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { - this.downloadedBlockStatesData = data - // todo - this.renderUpdateEmitter.emit('blockStatesDownloaded') - resolve(data) + const loadBlockStates = async () => { + return new Promise(resolve => { + if (this.customBlockStatesData) return resolve(this.customBlockStatesData) + return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { + this.downloadedBlockStatesData = data + // todo + this.renderUpdateEmitter.emit('blockStatesDownloaded') + resolve(data) + }) }) - }) - } - loadBlockStates().then((blockStates) => { - for (const worker of this.workers) { - worker.postMessage({ type: 'blockStates', json: blockStates }) } + loadBlockStates().then((blockStates) => { + for (const worker of this.workers) { + worker.postMessage({ type: 'blockStates', json: blockStates }) + } + }) }) + } getLoadedChunksRelative (pos: Vec3) { From 253fb8a2b5bb33314cce99a01926dce437b76a26 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 21 Mar 2024 14:16:18 +0300 Subject: [PATCH 06/86] move code around, +export --- prismarine-viewer/examples/playground.ts | 319 +---------------------- prismarine-viewer/viewer/lib/viewer.ts | 40 +-- src/playerWindows.ts | 2 +- 3 files changed, 30 insertions(+), 331 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 693c4eefc..a834b28d9 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -13,18 +13,8 @@ import JSZip from 'jszip' import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' // import * as Mathgl from 'math.gl' -import { m4 } from 'twgl.js' -import Stats from 'stats.js' - -//@ts-ignore -import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' -//@ts-ignore -import Stone from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/stone.png' - -//@ts-ignore -import VertShader from './_VertexShader.vert' -//@ts-ignore -import FragShader from './_FragmentShader.frag' +import { findTextureInBlockStates } from '../../src/playerWindows' +import { initWeblRenderer } from './webglRenderer' globalThis.THREE = THREE //@ts-ignore @@ -95,8 +85,7 @@ async function main() { const mcData = require('minecraft-data')(version) window['loadedData'] = mcData -const stats = new Stats() -gui.add(params, 'version', globalThis.includedVersions) + gui.add(params, 'version', globalThis.includedVersions) gui.add(params, 'block', mcData.blocksArray.map(b => b.name).sort((a, b) => a.localeCompare(b))) const metadataGui = gui.add(params, 'metadata') gui.add(params, 'supportBlock') @@ -139,279 +128,10 @@ gui.add(params, 'version', globalThis.includedVersions) // await schem.paste(world, new Vec3(0, 60, 0)) const worldView = new WorldDataEmitter(world, viewDistance, targetPos) - - const canvas = document.createElement('canvas') - const gl = canvas.getContext('webgl2')! - - const program = createProgram(gl, VertShader, FragShader) - const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000) - - let vertices = new Float32Array([ - -0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 1.0, 0.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - -0.5, 0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, -0.5, 0.0, 0.0, - - -0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, 0.5, 1.0, 0.0, - 0.5, 0.5, 0.5, 1.0, 1.0, - 0.5, 0.5, 0.5, 1.0, 1.0, - -0.5, 0.5, 0.5, 0.0, 1.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - - -0.5, 0.5, 0.5, 1.0, 0.0, - -0.5, 0.5, -0.5, 1.0, 1.0, - -0.5, -0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, 0.5, 1.0, 0.0, - - 0.5, 0.5, 0.5, 1.0, 0.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - 0.5, -0.5, -0.5, 0.0, 1.0, - 0.5, -0.5, -0.5, 0.0, 1.0, - 0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 1.0, 0.0, - - -0.5, -0.5, -0.5, 0.0, 1.0, - 0.5, -0.5, -0.5, 1.0, 1.0, - 0.5, -0.5, 0.5, 1.0, 0.0, - 0.5, -0.5, 0.5, 1.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 1.0, - - -0.5, 0.5, -0.5, 0.0, 1.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - 0.5, 0.5, 0.5, 1.0, 0.0, - 0.5, 0.5, 0.5, 1.0, 0.0, - -0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, -0.5, 0.0, 1.0 - ]) - - let CubePositions = [] as any - - //write random coordinates to cube positions xyz ten cubes; - for (let i = 0; i < 100_000; i++) { - let x = Math.random() * 100 - 50; - let y = Math.random() * 100 - 50; - let z = Math.random() * 100 - 100; - CubePositions.push([x, y, z]); - } - - let VBO, VAO = gl.createVertexArray(); - VBO = gl.createBuffer(); - //EBO = gl.createBuffer(); - - gl.bindVertexArray(VAO); - gl.bindBuffer(gl.ARRAY_BUFFER, VBO) - gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) - - //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) - //gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) - - gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) - gl.enableVertexAttribArray(0) - - gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) - gl.enableVertexAttribArray(1) - - //gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) - //gl.enableVertexAttribArray(2) - - - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.bindVertexArray(null) - - let image = new Image(); - // simple black white chess image 10x10 - image.src = Dirt - let image2 = new Image(); - // simple black white chess image 10x10 - image2.src = '/textures/1.18.1.png' - - console.log(image.src) - await new Promise((resolve) => { - image.onload = resolve - }) - await new Promise((resolve) => { - image2.onload = resolve - }) - - let pitch = 0, yaw = 0; - let x = 0, y = 0, z = 0; - - const keys = (e) => { - const code = e.code - const pressed = e.type === 'keydown' - if (pressed) { - if (code === 'KeyW') { - z--; - } - if (code === 'KeyS') { - z++; - } - if (code === 'KeyA') { - x--; - } - if (code === 'KeyD') { - x++; - } - } - } - window.addEventListener('keydown', keys) - window.addEventListener('keyup', keys) - - // mouse - const mouse = { x: 0, y: 0 } - const mouseMove = (e) => { - if (e.buttons === 1) { - yaw += e.movementY/20; - pitch += e.movementX/20; - } - } - window.addEventListener('mousemove', mouseMove) - const viewer = new Viewer(null as any | null, 1) globalThis.viewer = viewer - viewer.world.texturesVersion = ('1.18.1') - viewer.world.updateTexturesData() - await new Promise(resolve => { - // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) - // viewer.world.material.map!.image.onload = () => { - // console.log(this.material.map!.image) - // resolve() - // } - viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) - }) - console.log(viewer.world.downloadedBlockStatesData) - const names = Object.keys(viewer.world.downloadedBlockStatesData) - let texture1 = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture1); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - //.tset texture fgl.ering paramegl.s - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image.width, image.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image); - //gl.generateMipmap(gl.TEXTURE_2D); - - let texture2 = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture2); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - //.tset texture fgl.ering paramegl.s - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image2.width, image2.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image2); - //gl.generateMipmap(gl.TEXTURE_2D); - - gl.useProgram(program) - - gl.uniform1i(gl.getUniformLocation(program, "texture1"), 0); - gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); - - //gl.attachShader(program, program) - gl.enable(gl.DEPTH_TEST) - //gl.generateMipmap() - //gl.enable(gl) - //gl.clearColor(0, 0, 0, 1) - //gl.clear(gl.COLOR_BUFFER_BIT) - document.body.appendChild(canvas) - - let view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) - const projection = m4.perspective(75 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 512) - // view = m4.identity(); - // m4.rotateX(view, yaw * Math.PI / 180) - // m4.rotateY(view, pitch * Math.PI / 180) - // m4.translate(view, [x,y,z], view) - let ModelUniform = gl.getUniformLocation(program, "model") - let uvUniform = gl.getUniformLocation(program, "uv"); - let ViewUniform = gl.getUniformLocation(program, "view") - let ProjectionUniform = gl.getUniformLocation(program, "projection") - - // stats.addPanel(new Stats.Panel('FPS', '#0ff', '#002')) - document.body.appendChild(stats.dom) - const loop = (performance) => { - stats.begin() - gl.canvas.width = window.innerWidth * window.devicePixelRatio - gl.canvas.height = window.innerHeight * window.devicePixelRatio - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) - - view = m4.identity(); - m4.rotateX(view, yaw * Math.PI / 180, view) - m4.rotateY(view, pitch * Math.PI / 180, view) - m4.translate(view, [x,y,z], view) - - gl.clearColor(0.1, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT) - gl.clear(gl.DEPTH_BUFFER_BIT) - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture1); - gl.activeTexture(gl.TEXTURE1); - gl.bindTexture(gl.TEXTURE_2D, texture2); - - gl.useProgram(program) - - gl.uniformMatrix4fv(ViewUniform, false, view); - gl.uniformMatrix4fv(ProjectionUniform, false, projection); - - - - gl.bindVertexArray(VAO) - - - let i = 0 - CubePositions.forEach((cubePosition) => { - const model = m4.identity() - - m4.translate(model, [cubePosition[0], cubePosition[1], cubePosition[2]], model); - //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); - //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) - //m4.rotateZ(model, Math.random() / 1010, model) - gl.uniformMatrix4fv(ModelUniform, false, model); - gl.uniform2fv(uvUniform, [i%64 * 1/64,parseInt(i/64) * 1/64]); - - // let result - // i %= names.length / 2 - // for (const name of ['stone']) { - // result = viewer.world.downloadedBlockStatesData[name]?.variants?.['']?.[0]?.model?.elements?.[0]?.faces?.north?.texture - // i++ - // if (result) break - // } - // const - // const tileSize = image.width - // const blocks = - // result = { - // v: 3*1/64, - // u: 4*1/64 - // } - - i++ - i %= 800; - - - gl.drawArrays(gl.TRIANGLES, 0, 36); - }) - ///model.translate([0, 0, 0], model) - - requestAnimationFrame(loop) - //gl.Swa - stats.end() - } - loop(performance.now) - - // gl.deleteVertexArray(VAO); - // gl.deleteBuffer(VBO) - // gl.deleteBuffer(EBO) - // gl.deleteProgram(program) + initWeblRenderer() return @@ -461,34 +181,3 @@ gui.add(params, 'version', globalThis.includedVersions) // }) } main() - -export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmentShader: string) => { - const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => { - const shaderName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment' - const shader = gl.createShader(type)! - gl.shaderSource(shader, source) - gl.compileShader(shader) - - const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) - if (!success) { - const info = gl.getShaderInfoLog(shader) - gl.deleteShader(shader) - throw new Error(`Shader ${shaderName} compile error: ` + info) - } - return shader - } - - - - const program = gl.createProgram()! - gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) - gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)!) - gl.linkProgram(program) - const linkSuccess = gl.getProgramParameter(program, gl.LINK_STATUS) - if (!linkSuccess) { - const info = gl.getProgramInfoLog(program) - gl.deleteProgram(program) - throw new Error('Program link error: ' + info) - } - return program -} diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index d4c4f066f..f58a7ba3c 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,9 +7,7 @@ import { Primitives } from './primitives' import { getVersion } from './version' import EventEmitter from 'events' import { EffectComposer, RenderPass, ShaderPass, FXAAShader } from 'three-stdlib' -import { WorldHolder } from './worldrenderer' -THREE.ShaderChunk export class Viewer { scene: THREE.Scene ambientLight: THREE.AmbientLight @@ -30,10 +28,22 @@ export class Viewer { fxaaPass: ShaderPass renderPass: RenderPass - constructor(public holder: WorldHolder, numWorkers?: number, public enableFXAA = false) { - this.world = new WorldRenderer(holder, numWorkers) - // this.entities = new Entities(this.scene) - // this.primitives = new Primitives(this.scene, this.camera) + constructor(public renderer: THREE.WebGLRenderer, numWorkers?: number, public enableFXAA = false) { + // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 + THREE.ColorManagement.enabled = false + renderer.outputColorSpace = THREE.LinearSRGBColorSpace + + this.scene = new THREE.Scene() + this.scene.matrixAutoUpdate = false // for perf + this.resetScene() + if (this.enableFXAA) { + this.enableFxaaScene() + } + this.world = new WorldRenderer(this.scene, numWorkers) + this.entities = new Entities(this.scene) + this.primitives = new Primitives(this.scene, this.camera) + + this.domElement = renderer.domElement } resetScene () { @@ -182,15 +192,15 @@ export class Viewer { tweenJs.update() } - // render () { - // if (this.composer) { - // this.renderPass.camera = this.camera - // this.composer.render() - // } else { - // this.renderer.render(this.scene, this.camera) - // } - // this.entities.render() - // } + render () { + if (this.composer) { + this.renderPass.camera = this.camera + this.composer.render() + } else { + this.renderer.render(this.scene, this.camera) + } + this.entities.render() + } async waitForChunksToRender () { await this.world.waitForChunksToRender() diff --git a/src/playerWindows.ts b/src/playerWindows.ts index bdbe34a17..a0e08e996 100644 --- a/src/playerWindows.ts +++ b/src/playerWindows.ts @@ -127,7 +127,7 @@ export const onGameLoad = (onLoad) => { }) } -const findTextureInBlockStates = (name) => { +export const findTextureInBlockStates = (name) => { assertDefined(viewer) const blockStates: BlockStates = viewer.world.customBlockStatesData || viewer.world.downloadedBlockStatesData const vars = blockStates[name]?.variants From 024891c6d572a6f5e9fcf2f62955fa0e771c1c79 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 21 Mar 2024 15:47:27 +0300 Subject: [PATCH 07/86] add --- prismarine-viewer/examples/webglRenderer.ts | 315 ++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 prismarine-viewer/examples/webglRenderer.ts diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts new file mode 100644 index 000000000..ef57a8762 --- /dev/null +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -0,0 +1,315 @@ +import * as THREE from 'three' +import { m4 } from 'twgl.js' +import Stats from 'stats.js' + +//@ts-ignore +import VertShader from './_VertexShader.vert' +//@ts-ignore +import FragShader from './_FragmentShader.frag' + +//@ts-ignore +import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' +//@ts-ignore +import Stone from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/stone.png' +import { Viewer } from '../viewer/lib/viewer' +import { findTextureInBlockStates } from '../../src/playerWindows' + +declare const viewer: Viewer + +let renderLoop +export const makeRender = () => { + renderLoop() +} + +export const initWeblRenderer = async () => { + const stats = new Stats() + const canvas = document.createElement('canvas') + const gl = canvas.getContext('webgl2')! + + const program = createProgram(gl, VertShader, FragShader) + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000) + + let vertices = new Float32Array([ + -0.5, -0.5, -0.5, 0.0, 0.0, + 0.5, -0.5, -0.5, 1.0, 0.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + -0.5, 0.5, -0.5, 0.0, 1.0, + -0.5, -0.5, -0.5, 0.0, 0.0, + + -0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, -0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 1.0, + 0.5, 0.5, 0.5, 1.0, 1.0, + -0.5, 0.5, 0.5, 0.0, 1.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + + -0.5, 0.5, 0.5, 1.0, 0.0, + -0.5, 0.5, -0.5, 1.0, 1.0, + -0.5, -0.5, -0.5, 0.0, 1.0, + -0.5, -0.5, -0.5, 0.0, 1.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, 0.5, 1.0, 0.0, + + 0.5, 0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + 0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, 0.5, 0.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, + + -0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, -0.5, 1.0, 1.0, + 0.5, -0.5, 0.5, 1.0, 0.0, + 0.5, -0.5, 0.5, 1.0, 0.0, + -0.5, -0.5, 0.5, 0.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 1.0, + + -0.5, 0.5, -0.5, 0.0, 1.0, + 0.5, 0.5, -0.5, 1.0, 1.0, + 0.5, 0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 1.0 + ]) + + let CubePositions = [] as any + + //write random coordinates to cube positions xyz ten cubes; + for (let i = 0; i < 100_000; i++) { + let x = Math.random() * 100 - 50; + let y = Math.random() * 100 - 50; + let z = Math.random() * 100 - 100; + CubePositions.push([x, y, z]); + } + + let VBO, VAO = gl.createVertexArray(); + VBO = gl.createBuffer(); + //EBO = gl.createBuffer(); + + gl.bindVertexArray(VAO); + gl.bindBuffer(gl.ARRAY_BUFFER, VBO) + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) + + //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) + //gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) + + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) + gl.enableVertexAttribArray(0) + + gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) + gl.enableVertexAttribArray(1) + + //gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) + //gl.enableVertexAttribArray(2) + + + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindVertexArray(null) + + let image = new Image(); + // simple black white chess image 10x10 + image.src = Dirt + let image2 = new Image(); + // simple black white chess image 10x10 + image2.src = '/textures/1.18.1.png' + + console.log(image.src) + await new Promise((resolve) => { + image.onload = resolve + }) + await new Promise((resolve) => { + image2.onload = resolve + }) + + let pitch = 0, yaw = 0; + let x = 0, y = 0, z = 0; + + const keys = (e) => { + const code = e.code + const pressed = e.type === 'keydown' + if (pressed) { + if (code === 'KeyW') { + z--; + } + if (code === 'KeyS') { + z++; + } + if (code === 'KeyA') { + x--; + } + if (code === 'KeyD') { + x++; + } + } + } + window.addEventListener('keydown', keys) + window.addEventListener('keyup', keys) + + // mouse + const mouse = { x: 0, y: 0 } + const mouseMove = (e) => { + if (e.buttons === 1) { + yaw += e.movementY / 20; + pitch += e.movementX / 20; + } + } + window.addEventListener('mousemove', mouseMove) + + viewer.world.texturesVersion = ('1.18.1') + viewer.world.updateTexturesData() + await new Promise(resolve => { + // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) + // viewer.world.material.map!.image.onload = () => { + // console.log(this.material.map!.image) + // resolve() + // } + viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) + }) + console.log(viewer.world.downloadedBlockStatesData) + const names = Object.keys(viewer.world.downloadedBlockStatesData) + + let texture1 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture1); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + //.tset texture fgl.ering paramegl.s + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image.width, image.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image); + //gl.generateMipmap(gl.TEXTURE_2D); + + let texture2 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture2); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + //.tset texture fgl.ering paramegl.s + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image2.width, image2.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image2); + //gl.generateMipmap(gl.TEXTURE_2D); + + gl.useProgram(program) + + gl.uniform1i(gl.getUniformLocation(program, "texture1"), 0); + gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); + + //gl.attachShader(program, program) + gl.enable(gl.DEPTH_TEST) + //gl.generateMipmap() + //gl.enable(gl) + //gl.clearColor(0, 0, 0, 1) + //gl.clear(gl.COLOR_BUFFER_BIT) + document.body.appendChild(canvas) + + let view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) + const projection = m4.perspective(75 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 512) + // view = m4.identity(); + // m4.rotateX(view, yaw * Math.PI / 180) + // m4.rotateY(view, pitch * Math.PI / 180) + // m4.translate(view, [x,y,z], view) + let ModelUniform = gl.getUniformLocation(program, "model") + let uvUniform = gl.getUniformLocation(program, "uv"); + let ViewUniform = gl.getUniformLocation(program, "view") + let ProjectionUniform = gl.getUniformLocation(program, "projection") + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture1); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, texture2); + + // stats.addPanel(new Stats.Panel('FPS', '#0ff', '#002')) + document.body.appendChild(stats.dom) + renderLoop = (performance) => { + stats.begin() + gl.canvas.width = window.innerWidth * window.devicePixelRatio + gl.canvas.height = window.innerHeight * window.devicePixelRatio + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) + + view = m4.identity(); + m4.rotateX(view, yaw * Math.PI / 180, view) + m4.rotateY(view, pitch * Math.PI / 180, view) + m4.translate(view, [x, y, z], view) + + gl.clearColor(0.1, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT) + gl.clear(gl.DEPTH_BUFFER_BIT) + + gl.useProgram(program) + + gl.uniformMatrix4fv(ViewUniform, false, view); + gl.uniformMatrix4fv(ProjectionUniform, false, projection); + + + + gl.bindVertexArray(VAO) + + + let i = 0 + CubePositions.forEach((cubePosition) => { + const model = m4.identity() + + m4.translate(model, [cubePosition[0], cubePosition[1], cubePosition[2]], model); + //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); + //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) + //m4.rotateZ(model, Math.random() / 1010, model) + const result = findTextureInBlockStates(i % 2 ? 'dirt' : 'cobblestone')?.north.texture! + gl.uniformMatrix4fv(ModelUniform, false, model); + // const u = 4 * 1 / 64; + // const v = 0 * 1 / 64; + const u = result.u + result.su + const v = result.v + gl.uniform2fv(uvUniform, [u, v]) + + // i++ + // i %= 800; + + gl.drawArrays(gl.TRIANGLES, 0, 36); + }) + ///model.translate([0, 0, 0], model) + + //gl.Swa + stats.end() + } + + // gl.deleteVertexArray(VAO); + // gl.deleteBuffer(VBO) + // gl.deleteBuffer(EBO) + // gl.deleteProgram(program) + +} + +export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmentShader: string) => { + const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => { + const shaderName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment' + const shader = gl.createShader(type)! + gl.shaderSource(shader, source) + gl.compileShader(shader) + + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) + if (!success) { + const info = gl.getShaderInfoLog(shader) + gl.deleteShader(shader) + throw new Error(`Shader ${shaderName} compile error: ` + info) + } + return shader + } + + + + const program = gl.createProgram()! + gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) + gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)!) + gl.linkProgram(program) + const linkSuccess = gl.getProgramParameter(program, gl.LINK_STATUS) + if (!linkSuccess) { + const info = gl.getProgramInfoLog(program) + gl.deleteProgram(program) + throw new Error('Program link error: ' + info) + } + return program +} From 73a5e9d8ba82d11178c53caa76df09e05cf92375 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 21 Mar 2024 22:50:44 +0300 Subject: [PATCH 08/86] up render --- prismarine-viewer/buildWorker.mjs | 4 +- prismarine-viewer/examples/playground.ts | 50 +++++++++-------- prismarine-viewer/examples/webglRenderer.ts | 43 ++++++++------- prismarine-viewer/viewer/lib/models.ts | 54 ++++++++++++++++--- prismarine-viewer/viewer/lib/viewer.ts | 16 +++--- prismarine-viewer/viewer/lib/worldrenderer.ts | 49 +++++++++-------- 6 files changed, 134 insertions(+), 82 deletions(-) diff --git a/prismarine-viewer/buildWorker.mjs b/prismarine-viewer/buildWorker.mjs index a8a5c1381..9be06cf23 100644 --- a/prismarine-viewer/buildWorker.mjs +++ b/prismarine-viewer/buildWorker.mjs @@ -21,7 +21,7 @@ const buildOptions = { }, platform: 'browser', entryPoints: [path.join(__dirname, './viewer/lib/worker.js')], - minify: true, + // minify: true, logLevel: 'info', drop: !watch ? [ 'debugger' @@ -101,7 +101,7 @@ const buildOptions = { resolveDir: process.cwd(), } }) - build.onEnd(({metafile, outputFiles}) => { + build.onEnd(({ metafile, outputFiles }) => { if (!metafile) return fs.writeFileSync(path.join(__dirname, './public/metafile.json'), JSON.stringify(metafile)) for (const outDir of ['../dist/', './public/']) { diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 60e87bc07..8839affbe 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -65,7 +65,8 @@ let ignoreResize = false async function main () { let continuousRender = false - const { version } = params + // const { version } = params + const version = '1.18.1' // temporary solution until web worker is here, cache data for faster reloads const globalMcData = window['mcData'] if (!globalMcData['version']) { @@ -128,12 +129,12 @@ async function main () { // await schem.paste(world, new Vec3(0, 60, 0)) const worldView = new WorldDataEmitter(world, viewDistance, targetPos) - const viewer = new Viewer(null as any | null, 1) + const nullRenderer = new THREE.WebGLRenderer({ antialias: true }) + const viewer = new Viewer(nullRenderer, 1) + viewer.setVersion(version) globalThis.viewer = viewer - initWeblRenderer() - - return + await initWeblRenderer(version) // Create viewer @@ -143,11 +144,6 @@ async function main () { window['worldView'] = worldView window['viewer'] = viewer - function degrees_to_radians (degrees) { - var pi = Math.PI; - return degrees * (pi / 180); - } - // const jsonData = await fetch('https://bluecolored.de/bluemap/maps/overworld/tiles/0/x-2/2/z1/6.json?584662').then(r => r.json()) // const uniforms = { @@ -182,16 +178,16 @@ async function main () { //@ts-ignore - const controls = new OrbitControls(viewer.camera, renderer.domElement) - controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5) + // const controls = new OrbitControls(viewer.camera, nullRenderer.domElement) + // 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) const yaw = THREE.MathUtils.degToRad(45) - 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) - controls.update() + // 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) + // controls.update() let blockProps = {} let entityOverrides = {} @@ -223,6 +219,8 @@ async function main () { }, TWEEN_DURATION) } + params.block ||= 'stone' + const onUpdate = { block () { metadataFolder.destroy() @@ -347,18 +345,18 @@ async function main () { // worldView.updatePosition(controls.target) viewer.update() viewer.render() - // window.requestAnimationFrame(animate) + window.requestAnimationFrame(animate) } - viewer.world.renderUpdateEmitter.addListener('update', () => { - animate() - }) + // viewer.world.renderUpdateEmitter.addListener('update', () => { + // animate() + // }) animate() // #region camera rotation param if (params.camera) { const [x, y] = params.camera.split(',') viewer.camera.rotation.set(parseFloat(x), parseFloat(y), 0, 'ZYX') - controls.update() + // controls.update() console.log(viewer.camera.rotation.x, parseFloat(x)) } const throttledCamQsUpdate = _.throttle(() => { @@ -366,10 +364,10 @@ async function main () { // params.camera = `${camera.rotation.x.toFixed(2)},${camera.rotation.y.toFixed(2)}` setQs() }, 200) - controls.addEventListener('change', () => { - throttledCamQsUpdate() - animate() - }) + // controls.addEventListener('change', () => { + // throttledCamQsUpdate() + // animate() + // }) // #endregion const continuousUpdate = () => { @@ -390,7 +388,7 @@ async function main () { const { camera } = viewer viewer.camera.aspect = window.innerWidth / window.innerHeight viewer.camera.updateProjectionMatrix() - renderer.setSize(window.innerWidth, window.innerHeight) + nullRenderer.setSize(window.innerWidth, window.innerHeight) animate() } diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index ef57a8762..0b0e2c472 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -21,13 +21,19 @@ export const makeRender = () => { renderLoop() } -export const initWeblRenderer = async () => { +let CubePositions = [] as [number, number, number][] + +export const addCubes = (positions: [number, number, number][]) => { + CubePositions.push(...positions) +} + +export const initWeblRenderer = async (version) => { const stats = new Stats() const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2')! const program = createProgram(gl, VertShader, FragShader) - const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000) + // const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000) let vertices = new Float32Array([ -0.5, -0.5, -0.5, 0.0, 0.0, @@ -73,9 +79,7 @@ export const initWeblRenderer = async () => { -0.5, 0.5, -0.5, 0.0, 1.0 ]) - let CubePositions = [] as any - - //write random coordinates to cube positions xyz ten cubes; + // write random coordinates to cube positions xyz ten cubes; for (let i = 0; i < 100_000; i++) { let x = Math.random() * 100 - 50; let y = Math.random() * 100 - 50; @@ -112,7 +116,7 @@ export const initWeblRenderer = async () => { image.src = Dirt let image2 = new Image(); // simple black white chess image 10x10 - image2.src = '/textures/1.18.1.png' + image2.src = `/textures/${version}.png` console.log(image.src) await new Promise((resolve) => { @@ -122,24 +126,21 @@ export const initWeblRenderer = async () => { image2.onload = resolve }) - let pitch = 0, yaw = 0; - let x = 0, y = 0, z = 0; - const keys = (e) => { const code = e.code const pressed = e.type === 'keydown' if (pressed) { if (code === 'KeyW') { - z--; + viewer.camera.position.z -= 1 } if (code === 'KeyS') { - z++; + viewer.camera.position.z += 1 } if (code === 'KeyA') { - x--; + viewer.camera.position.x -= 1 } if (code === 'KeyD') { - x++; + viewer.camera.position.x += 1 } } } @@ -150,13 +151,16 @@ export const initWeblRenderer = async () => { const mouse = { x: 0, y: 0 } const mouseMove = (e) => { if (e.buttons === 1) { - yaw += e.movementY / 20; - pitch += e.movementX / 20; + viewer.camera.rotation.y += e.movementX / 100 + viewer.camera.rotation.x += e.movementY / 100 + console.log('viewer.camera.position', viewer.camera.position) + // yaw += e.movementY / 20; + // pitch += e.movementX / 20; } } window.addEventListener('mousemove', mouseMove) - viewer.world.texturesVersion = ('1.18.1') + viewer.world.texturesVersion = version viewer.world.updateTexturesData() await new Promise(resolve => { // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) @@ -186,7 +190,6 @@ export const initWeblRenderer = async () => { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - //.tset texture fgl.ering paramegl.s gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); @@ -231,9 +234,11 @@ export const initWeblRenderer = async () => { gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) view = m4.identity(); + const yaw = viewer.camera.rotation.y + const pitch = viewer.camera.rotation.x m4.rotateX(view, yaw * Math.PI / 180, view) m4.rotateY(view, pitch * Math.PI / 180, view) - m4.translate(view, [x, y, z], view) + m4.translate(view, [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z], view) gl.clearColor(0.1, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT) @@ -265,7 +270,7 @@ export const initWeblRenderer = async () => { const v = result.v gl.uniform2fv(uvUniform, [u, v]) - // i++ + i++ // i %= 800; gl.drawArrays(gl.TRIANGLES, 0, 36); diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 6e43597a0..16ed12041 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -388,7 +388,8 @@ export function getSectionGeometry (sx, sy, sz, world: World) { t_uvs: [], indices: [], // todo this can be removed here - signs: {} + signs: {}, + blocks: {} } as Record const cursor = new Vec3(0, 0, 0) @@ -417,13 +418,10 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } for (const variant of block.variant) { + console.log(variant) if (!variant || !variant.model) continue - if (block.name === 'water') { - renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) - } else if (block.name === 'lava') { - renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) - } else { + if (block.name !== 'water' && block.name !== 'lava'/* && block.isCube */) { let globalMatrix = null as any let globalShift = null as any @@ -440,9 +438,51 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } for (const element of variant.model.elements) { - renderElement(world, cursor, element, variant.model.ao, attr, globalMatrix, globalShift, block, biome) + for (const face in element.faces) { + const cullIfIdentical = block.name.indexOf('glass') >= 0 + + const eFace = element.faces[face] + const { corners, mask1, mask2 } = elemFaces[face] + const dir = matmul3(globalMatrix, elemFaces[face].dir) + + if (eFace.cullface) { + const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) + if (neighbor) { + if (cullIfIdentical && neighbor.type === block.type) continue + if (!neighbor.transparent && neighbor.isCube) continue + } else { + continue + } + } + + attr.blocks[`${cursor.x},${cursor.y},${cursor.z}`] = block.name + } } } + // if (block.name === 'water') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) + // } else if (block.name === 'lava') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) + // } else { + // let globalMatrix = null as any + // let globalShift = null as any + + // for (const axis of ['x', 'y', 'z']) { + // if (axis in variant) { + // if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) + // else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) + // } + // } + + // if (globalMatrix) { + // globalShift = [8, 8, 8] + // globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) + // } + + // for (const element of variant.model.elements) { + // renderElement(world, cursor, element, variant.model.ao, attr, globalMatrix, globalShift, block, biome) + // } + // } } } } diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index f58a7ba3c..e7fbd4b9b 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,6 +7,7 @@ import { Primitives } from './primitives' import { getVersion } from './version' import EventEmitter from 'events' import { EffectComposer, RenderPass, ShaderPass, FXAAShader } from 'three-stdlib' +import { makeRender } from '../../examples/webglRenderer' export class Viewer { scene: THREE.Scene @@ -193,13 +194,14 @@ export class Viewer { } render () { - if (this.composer) { - this.renderPass.camera = this.camera - this.composer.render() - } else { - this.renderer.render(this.scene, this.camera) - } - this.entities.render() + makeRender() + // if (this.composer) { + // this.renderPass.camera = this.camera + // this.composer.render() + // } else { + // this.renderer.render(this.scene, this.camera) + // } + // this.entities.render() } async waitForChunksToRender () { diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index 67e264e0b..9c811205e 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -10,13 +10,14 @@ import { toMajor } from './version.js' import PrismarineChatLoader from 'prismarine-chat' import { renderSign } from '../sign-renderer/' import { chunkPos, sectionPos } from './simpleUtils' +import { addCubes } from '../../examples/webglRenderer' function mod (x, n) { return ((x % n) + n) % n } export type WorldHolder = { - add(opt: { + add (opt: { geometry: { positions: Float32Array, normals: Float32Array, @@ -29,7 +30,7 @@ export type WorldHolder = { signs: Record } }) - remove(opt: { key: string }) + remove (opt: { key: string }) } export class WorldRenderer { @@ -61,7 +62,7 @@ export class WorldRenderer { promisesQueue = [] as Promise[] - constructor(public holder: WorldHolder, numWorkers = 4) { + constructor(public holder: unknown, numWorkers = 4) { // init workers for (let i = 0; i < numWorkers; i++) { // Node environment needs an absolute path, but browser needs the url of the file @@ -85,7 +86,12 @@ export class WorldRenderer { // if const chunkCoords = data.key.split(',') - if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return + if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return + + addCubes(Object.entries(data.geometry.blocks).map(([pos, block]) => { + return pos.split(',').map(Number) as [number, number, number] + })) + // if (!this.initialChunksLoad && this.enableChunksLoadDelay) { // const newPromise = new Promise(resolve => { @@ -238,12 +244,12 @@ export class WorldRenderer { } resetWorld () { - this.active = false - for (const mesh of Object.values(this.sectionObjects)) { - this.scene.remove(mesh) - } - this.sectionObjects = {} - this.loadedChunks = {} + // this.active = false + // for (const mesh of Object.values(this.sectionObjects)) { + // this.scene.remove(mesh) + // } + // this.sectionObjects = {} + // this.loadedChunks = {} this.sectionsOutstanding = new Set() for (const worker of this.workers) { worker.postMessage({ type: 'reset' }) @@ -251,19 +257,19 @@ export class WorldRenderer { } setVersion (version, texturesVersion = version) { - // this.version = version - // this.texturesVersion = texturesVersion - // this.resetWorld() - // this.active = true + this.version = version + this.texturesVersion = texturesVersion + this.resetWorld() + this.active = true - // const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] - // for (const worker of this.workers) { - // const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) - // mcData.version = JSON.parse(JSON.stringify(mcData.version)) - // worker.postMessage({ type: 'mcData', mcData, version: this.version }) - // } + const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] + for (const worker of this.workers) { + const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) + mcData.version = JSON.parse(JSON.stringify(mcData.version)) + worker.postMessage({ type: 'mcData', mcData, version: this.version }) + } - // this.updateTexturesData() + this.updateTexturesData() } updateTexturesData () { @@ -306,6 +312,7 @@ export class WorldRenderer { } addColumn (x, z, chunk) { + console.log('addColumn') this.initialChunksLoad = false this.loadedChunks[`${x},${z}`] = true for (const worker of this.workers) { From 2ee0ef348342ddc8367d97befb6b138e398b81a2 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 21 Mar 2024 23:42:33 +0300 Subject: [PATCH 09/86] pus render --- prismarine-viewer/examples/playground.ts | 6 +- prismarine-viewer/examples/webglRenderer.ts | 73 ++++++++++++------- prismarine-viewer/viewer/lib/worldrenderer.ts | 10 ++- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 8839affbe..f5a32d041 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -184,9 +184,9 @@ async function main () { const cameraPos = targetPos.offset(2, 2, 2) const pitch = THREE.MathUtils.degToRad(-45) const yaw = THREE.MathUtils.degToRad(45) - // 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) + 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) // controls.update() let blockProps = {} diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 0b0e2c472..b55bed417 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -21,10 +21,10 @@ export const makeRender = () => { renderLoop() } -let CubePositions = [] as [number, number, number][] +export const cubePositions = [] as [number, number, number, string][] export const addCubes = (positions: [number, number, number][]) => { - CubePositions.push(...positions) + // CubePositions.push(...positions) } export const initWeblRenderer = async (version) => { @@ -80,12 +80,12 @@ export const initWeblRenderer = async (version) => { ]) // write random coordinates to cube positions xyz ten cubes; - for (let i = 0; i < 100_000; i++) { - let x = Math.random() * 100 - 50; - let y = Math.random() * 100 - 50; - let z = Math.random() * 100 - 100; - CubePositions.push([x, y, z]); - } + // for (let i = 0; i < 100_000; i++) { + // let x = Math.random() * 100 - 50; + // let y = Math.random() * 100 - 50; + // let z = Math.random() * 100 - 100; + // CubePositions.push([x, y, z]); + // } let VBO, VAO = gl.createVertexArray(); VBO = gl.createBuffer(); @@ -142,6 +142,12 @@ export const initWeblRenderer = async (version) => { if (code === 'KeyD') { viewer.camera.position.x += 1 } + if (code === 'ShiftLeft') { + viewer.camera.position.y += 0.5 + } + if (code === 'Space') { + viewer.camera.position.y -= 0.5 + } } } window.addEventListener('keydown', keys) @@ -234,11 +240,12 @@ export const initWeblRenderer = async (version) => { gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) view = m4.identity(); + // view = viewer.camera.matrix.elements const yaw = viewer.camera.rotation.y const pitch = viewer.camera.rotation.x m4.rotateX(view, yaw * Math.PI / 180, view) m4.rotateY(view, pitch * Math.PI / 180, view) - m4.translate(view, [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z], view) + m4.translate(view, [-viewer.camera.position.x, -viewer.camera.position.y, -viewer.camera.position.z], view) gl.clearColor(0.1, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT) @@ -255,25 +262,35 @@ export const initWeblRenderer = async (version) => { let i = 0 - CubePositions.forEach((cubePosition) => { - const model = m4.identity() - - m4.translate(model, [cubePosition[0], cubePosition[1], cubePosition[2]], model); - //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); - //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) - //m4.rotateZ(model, Math.random() / 1010, model) - const result = findTextureInBlockStates(i % 2 ? 'dirt' : 'cobblestone')?.north.texture! - gl.uniformMatrix4fv(ModelUniform, false, model); - // const u = 4 * 1 / 64; - // const v = 0 * 1 / 64; - const u = result.u + result.su - const v = result.v - gl.uniform2fv(uvUniform, [u, v]) - - i++ - // i %= 800; - - gl.drawArrays(gl.TRIANGLES, 0, 36); + // CubePositions = [[ + // 2, 90, 2 + // ]] + const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { + return Object.entries(chunk.blocks).map(([pos, block]) => { + return [...pos.split(',').map(Number), block] as [number, number, number, string] + }) + }).flat() + cubePositions.forEach(([x, y, z, name]) => { + const result = findTextureInBlockStates(name)?.north.texture! + if (result) { + const model = m4.identity() + + //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); + //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) + //m4.rotateZ(model, Math.random() / 1010, model) + m4.translate(model, [x, y, z], model); + gl.uniformMatrix4fv(ModelUniform, false, model); + // const u = 4 * 1 / 64; + // const v = 0 * 1 / 64; + const u = result.u + result.su + const v = result.v + gl.uniform2fv(uvUniform, [u, v]) + + i++ + // i %= 800; + + gl.drawArrays(gl.TRIANGLES, 0, 36); + } }) ///model.translate([0, 0, 0], model) diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index 9c811205e..1847a0326 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -10,7 +10,7 @@ import { toMajor } from './version.js' import PrismarineChatLoader from 'prismarine-chat' import { renderSign } from '../sign-renderer/' import { chunkPos, sectionPos } from './simpleUtils' -import { addCubes } from '../../examples/webglRenderer' +import { addCubes, cubePositions } from '../../examples/webglRenderer' function mod (x, n) { return ((x % n) + n) % n @@ -57,6 +57,7 @@ export class WorldRenderer { droppedFpsPercentage = 0 initialChunksLoad = true enableChunksLoadDelay = false + newChunks = {} texturesVersion?: string @@ -88,9 +89,10 @@ export class WorldRenderer { const chunkCoords = data.key.split(',') if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return - addCubes(Object.entries(data.geometry.blocks).map(([pos, block]) => { - return pos.split(',').map(Number) as [number, number, number] - })) + this.newChunks[data.key] = data.geometry + // cubePositions.push(...Object.entries(data.geometry.blocks).map(([pos, block]) => { + // return [...pos.split(',').map(Number), block] as [number, number, number, string] + // })) // if (!this.initialChunksLoad && this.enableChunksLoadDelay) { From 027f5c691748641df5adb877dcfc38d14271651d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Fri, 22 Mar 2024 00:02:11 +0300 Subject: [PATCH 10/86] up package json --- package.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 73934df08..a4cb10380 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0-dev", "description": "A minecraft client running in a browser", "scripts": { - "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node 4 --watch", + "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --watch", "start-watch-script": "nodemon -w esbuild.mjs --watch", "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod", "check-build": "tsc && pnpm test-unit && pnpm build", @@ -31,6 +31,8 @@ "@floating-ui/react": "^0.26.1", "@mui/base": "5.0.0-beta.34", "@nxg-org/mineflayer-tracker": "^1.2.1", + "@react-oauth/google": "^0.12.1", + "@types/gapi": "^0.0.47", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@types/wicg-file-system-access": "^2023.10.2", @@ -47,8 +49,9 @@ "esbuild": "^0.19.3", "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.9", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.15", "fs-extra": "^11.1.1", + "google-drive-browserfs": "github:zardoy/browserfs#google-drive", "iconify-icon": "^1.0.8", "jszip": "^3.10.1", "lit": "^2.8.0", @@ -60,6 +63,7 @@ "node-gzip": "^1.1.2", "peerjs": "^1.5.0", "pretty-bytes": "^6.1.1", + "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -71,7 +75,6 @@ "stats.js": "^0.17.0", "tabbable": "^6.2.0", "title-case": "3.x", - "twgl.js": "^5.5.4", "ua-parser-js": "^1.0.37", "valtio": "^1.11.1", "workbox-build": "^7.0.0" From ad5a906a95e7670d0ebea32554c998e12169b05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Fri, 22 Mar 2024 00:31:24 +0300 Subject: [PATCH 11/86] fix things --- esbuild.mjs | 4 ++- package.json | 1 + prismarine-viewer/esbuild.mjs | 1 + prismarine-viewer/examples/playground.ts | 9 +++--- prismarine-viewer/examples/webglRenderer.ts | 34 +++++++++++---------- prismarine-viewer/sharedBuildOptions.mjs | 3 ++ src/globals.d.ts | 8 +++++ 7 files changed, 39 insertions(+), 21 deletions(-) create mode 100644 prismarine-viewer/sharedBuildOptions.mjs diff --git a/esbuild.mjs b/esbuild.mjs index 2d18ae3a0..e54623f73 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -76,7 +76,9 @@ const buildOptions = { loader: { // todo use external or resolve issues with duplicating '.png': 'dataurl', - '.map': 'empty' + '.map': 'empty', + '.vert': 'text', + '.frag': 'text' }, write: false, // todo would be better to enable? diff --git a/package.json b/package.json index a4cb10380..c9f632f2d 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "stats.js": "^0.17.0", "tabbable": "^6.2.0", "title-case": "3.x", + "twgl.js": "^5.5.4", "ua-parser-js": "^1.0.37", "valtio": "^1.11.1", "workbox-build": "^7.0.0" diff --git a/prismarine-viewer/esbuild.mjs b/prismarine-viewer/esbuild.mjs index 1be3ca498..4a87d9ff8 100644 --- a/prismarine-viewer/esbuild.mjs +++ b/prismarine-viewer/esbuild.mjs @@ -47,6 +47,7 @@ const buildOptions = { http: 'http-browserify', stream: 'stream-browserify', net: 'net-browserify', + 'stats.js': 'node_modules/stats.js/src/Stats.js', }, inject: [], metafile: true, diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index f5a32d041..cb9c7bd3c 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -340,17 +340,18 @@ async function main () { gui.openAnimated() }) - const animate = () => { + const animate = () => {} + const animate2 = () => { // if (controls) controls.update() // worldView.updatePosition(controls.target) viewer.update() viewer.render() - window.requestAnimationFrame(animate) + window.requestAnimationFrame(animate2) } // viewer.world.renderUpdateEmitter.addListener('update', () => { // animate() // }) - animate() + animate2() // #region camera rotation param if (params.camera) { @@ -374,7 +375,7 @@ async function main () { if (continuousRender) { animate() } - requestAnimationFrame(continuousUpdate) + // requestAnimationFrame(continuousUpdate) } continuousUpdate() diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index b55bed417..407d2e757 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -33,7 +33,6 @@ export const initWeblRenderer = async (version) => { const gl = canvas.getContext('webgl2')! const program = createProgram(gl, VertShader, FragShader) - // const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000) let vertices = new Float32Array([ -0.5, -0.5, -0.5, 0.0, 0.0, @@ -80,12 +79,12 @@ export const initWeblRenderer = async (version) => { ]) // write random coordinates to cube positions xyz ten cubes; - // for (let i = 0; i < 100_000; i++) { - // let x = Math.random() * 100 - 50; - // let y = Math.random() * 100 - 50; - // let z = Math.random() * 100 - 100; - // CubePositions.push([x, y, z]); - // } + for (let i = 0; i < 100_000; i++) { + let x = Math.random() * 100 - 50; + let y = Math.random() * 100 - 50; + let z = Math.random() * 100 - 100; + cubePositions.push([x, y, z, 'stone']); + } let VBO, VAO = gl.createVertexArray(); VBO = gl.createBuffer(); @@ -157,12 +156,15 @@ export const initWeblRenderer = async (version) => { const mouse = { x: 0, y: 0 } const mouseMove = (e) => { if (e.buttons === 1) { - viewer.camera.rotation.y += e.movementX / 100 - viewer.camera.rotation.x += e.movementY / 100 + viewer.camera.rotation.y += e.movementX / 50 + viewer.camera.rotation.x += e.movementY / 50 console.log('viewer.camera.position', viewer.camera.position) // yaw += e.movementY / 20; // pitch += e.movementX / 20; } + if (e.buttons === 2) { + viewer.camera.position.set(0, 0, 0) + } } window.addEventListener('mousemove', mouseMove) @@ -241,8 +243,8 @@ export const initWeblRenderer = async (version) => { view = m4.identity(); // view = viewer.camera.matrix.elements - const yaw = viewer.camera.rotation.y - const pitch = viewer.camera.rotation.x + const yaw = viewer.camera.rotation.x + const pitch = viewer.camera.rotation.y m4.rotateX(view, yaw * Math.PI / 180, view) m4.rotateY(view, pitch * Math.PI / 180, view) m4.translate(view, [-viewer.camera.position.x, -viewer.camera.position.y, -viewer.camera.position.z], view) @@ -265,11 +267,11 @@ export const initWeblRenderer = async (version) => { // CubePositions = [[ // 2, 90, 2 // ]] - const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { - return Object.entries(chunk.blocks).map(([pos, block]) => { - return [...pos.split(',').map(Number), block] as [number, number, number, string] - }) - }).flat() + // const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { + // return Object.entries(chunk.blocks).map(([pos, block]) => { + // return [...pos.split(',').map(Number), block] as [number, number, number, string] + // }) + // }).flat() cubePositions.forEach(([x, y, z, name]) => { const result = findTextureInBlockStates(name)?.north.texture! if (result) { diff --git a/prismarine-viewer/sharedBuildOptions.mjs b/prismarine-viewer/sharedBuildOptions.mjs new file mode 100644 index 000000000..52b2fb8fc --- /dev/null +++ b/prismarine-viewer/sharedBuildOptions.mjs @@ -0,0 +1,3 @@ +export const sharedPlaygroundMainOptions = { + alias: {} +} \ No newline at end of file diff --git a/src/globals.d.ts b/src/globals.d.ts index 75c9d0a0f..c2c5ac6b3 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -72,6 +72,14 @@ declare module '*.png' { const png: string export default png } +declare module '*.frag' { + const png: string + export default png +} +declare module '*.vert' { + const png: string + export default png +} interface PromiseConstructor { withResolvers (): { From 70c9686f89932d925804aaa909f810ec0a476964 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 22 Mar 2024 01:31:22 +0300 Subject: [PATCH 12/86] insane instace shading --- prismarine-viewer/examples/_VertexShader.vert | 7 +- prismarine-viewer/examples/playground.ts | 4 +- prismarine-viewer/examples/webglRenderer.ts | 201 ++++++++++-------- 3 files changed, 119 insertions(+), 93 deletions(-) diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index ce5fee718..8bec46229 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -1,16 +1,19 @@ #version 300 es layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 aTexCoord; +layout (location = 2) in vec3 aOffset; +layout (location = 3) in int TextureIndex; out vec2 TexCoord; -uniform mat4 model; +//uniform mat4 model; uniform mat4 view; uniform mat4 projection; //uniform vec4 uv; void main() { - gl_Position = projection * view * model * vec4(aPos, 1.0f); + gl_Position = projection * view * vec4(aPos + aOffset, 1.0f); TexCoord = vec2(aTexCoord.x, (1.0 - aTexCoord.y)); + } diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index cb9c7bd3c..51246180a 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -186,7 +186,7 @@ async function main () { const yaw = THREE.MathUtils.degToRad(45) 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) + viewer.camera.position.set(0, 0, 0) // controls.update() let blockProps = {} @@ -340,7 +340,7 @@ async function main () { gui.openAnimated() }) - const animate = () => {} + const animate = () => { } const animate2 = () => { // if (controls) controls.update() // worldView.updatePosition(controls.target) diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 407d2e757..02a2ab9da 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -21,11 +21,7 @@ export const makeRender = () => { renderLoop() } -export const cubePositions = [] as [number, number, number, string][] - -export const addCubes = (positions: [number, number, number][]) => { - // CubePositions.push(...positions) -} +export const cubePositions = [] as [number, number, number, string | null][] export const initWeblRenderer = async (version) => { const stats = new Stats() @@ -35,57 +31,67 @@ export const initWeblRenderer = async (version) => { const program = createProgram(gl, VertShader, FragShader) let vertices = new Float32Array([ - -0.5, -0.5, -0.5, 0.0, 0.0, - 0.5, -0.5, -0.5, 1.0, 0.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - -0.5, 0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, -0.5, 0.0, 0.0, - - -0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, -0.5, 0.5, 1.0, 0.0, - 0.5, 0.5, 0.5, 1.0, 1.0, - 0.5, 0.5, 0.5, 1.0, 1.0, - -0.5, 0.5, 0.5, 0.0, 1.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - - -0.5, 0.5, 0.5, 1.0, 0.0, - -0.5, 0.5, -0.5, 1.0, 1.0, - -0.5, -0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, -0.5, 0.0, 1.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, 0.5, 1.0, 0.0, - - 0.5, 0.5, 0.5, 1.0, 0.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - 0.5, -0.5, -0.5, 0.0, 1.0, - 0.5, -0.5, -0.5, 0.0, 1.0, - 0.5, -0.5, 0.5, 0.0, 0.0, - 0.5, 0.5, 0.5, 1.0, 0.0, - - -0.5, -0.5, -0.5, 0.0, 1.0, - 0.5, -0.5, -0.5, 1.0, 1.0, - 0.5, -0.5, 0.5, 1.0, 0.0, - 0.5, -0.5, 0.5, 1.0, 0.0, - -0.5, -0.5, 0.5, 0.0, 0.0, - -0.5, -0.5, -0.5, 0.0, 1.0, - - -0.5, 0.5, -0.5, 0.0, 1.0, - 0.5, 0.5, -0.5, 1.0, 1.0, - 0.5, 0.5, 0.5, 1.0, 0.0, - 0.5, 0.5, 0.5, 1.0, 0.0, - -0.5, 0.5, 0.5, 0.0, 0.0, - -0.5, 0.5, -0.5, 0.0, 1.0 + -0.5, -0.5, -0.5, 0.0, 0.0, // Bottom-let + 0.5, -0.5, -0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + -0.5, 0.5, -0.5, 0.0, 1.0, // top-let + -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let + // ront ace + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let + 0.5, 0.5, 0.5, 1.0, 1.0, // top-right + 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, 0.5, 1.0, 1.0, // top-right + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let + -0.5, 0.5, 0.5, 0.0, 1.0, // top-let + // Let ace + -0.5, 0.5, 0.5, 1.0, 0.0, // top-right + -0.5, -0.5, -0.5, 0.0, 1.0, // bottom-let + -0.5, 0.5, -0.5, 1.0, 1.0, // top-let + -0.5, -0.5, -0.5, 0.0, 1.0, // bottom-let + -0.5, 0.5, 0.5, 1.0, 0.0, // top-right + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-right + // Right ace + 0.5, 0.5, 0.5, 1.0, 0.0, // top-let + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, -0.5, -0.5, 0.0, 1.0, // bottom-right + 0.5, -0.5, -0.5, 0.0, 1.0, // bottom-right + 0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let + 0.5, 0.5, 0.5, 1.0, 0.0, // top-let + // Bottom ace + -0.5, -0.5, -0.5, 0.0, 1.0, // top-right + 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-let + 0.5, -0.5, -0.5, 1.0, 1.0, // top-let + 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-let + -0.5, -0.5, -0.5, 0.0, 1.0, // top-right + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-right + // Top ace + -0.5, 0.5, -0.5, 0.0, 1.0, // top-let + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, 0.5, 0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, 0.5, 1.0, 0.0, // bottom-right + -0.5, 0.5, 0.5, 0.0, 0.0, // bottom-let + -0.5, 0.5, -0.5, 0.0, 1.0 // top-let ]) + let NumberOfCube = 25_000 + + let cubePositions = new Float32Array(NumberOfCube * 3) + + // write random coordinates to cube positions xyz ten cubes; - for (let i = 0; i < 100_000; i++) { - let x = Math.random() * 100 - 50; - let y = Math.random() * 100 - 50; - let z = Math.random() * 100 - 100; - cubePositions.push([x, y, z, 'stone']); + for (let i = 0; i < NumberOfCube * 3; i += 3) { + cubePositions[i] = Math.random() * 100 - 50; + cubePositions[i + 1] = Math.random() * 100 - 50; + cubePositions[i + 2] = Math.random() * 100 - 100; + //cubePositions.push([x, y, z, null]); } + let instanceVBO = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + let VBO, VAO = gl.createVertexArray(); VBO = gl.createBuffer(); //EBO = gl.createBuffer(); @@ -94,21 +100,21 @@ export const initWeblRenderer = async (version) => { gl.bindBuffer(gl.ARRAY_BUFFER, VBO) gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) - //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, EBO) - //gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) - gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) gl.enableVertexAttribArray(0) gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) gl.enableVertexAttribArray(1) + //instance data - //gl.vertexAttribPointer(2,2,gl.FLOAT, false, 8*4 , 6*4) - //gl.enableVertexAttribArray(2) - - + gl.enableVertexAttribArray(2); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 3 * 4, 0); gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.bindVertexArray(null) + gl.vertexAttribDivisor(2, 1); + + //gl.bindBuffer(gl.ARRAY_BUFFER, null); + //gl.bindVertexArray(null) let image = new Image(); // simple black white chess image 10x10 @@ -156,8 +162,8 @@ export const initWeblRenderer = async (version) => { const mouse = { x: 0, y: 0 } const mouseMove = (e) => { if (e.buttons === 1) { - viewer.camera.rotation.y += e.movementX / 50 - viewer.camera.rotation.x += e.movementY / 50 + viewer.camera.rotation.y += e.movementX / 20 + viewer.camera.rotation.x += e.movementY / 20 console.log('viewer.camera.position', viewer.camera.position) // yaw += e.movementY / 20; // pitch += e.movementX / 20; @@ -186,7 +192,7 @@ export const initWeblRenderer = async (version) => { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - //.tset texture fgl.ering paramegl.s + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); @@ -206,11 +212,17 @@ export const initWeblRenderer = async (version) => { gl.useProgram(program) + + gl.uniform1i(gl.getUniformLocation(program, "texture1"), 0); gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); - //gl.attachShader(program, program) + gl.enable(gl.DEPTH_TEST) + gl.frontFace(gl.CCW) + gl.enable(gl.CULL_FACE) + + //gl.generateMipmap() //gl.enable(gl) //gl.clearColor(0, 0, 0, 1) @@ -228,11 +240,15 @@ export const initWeblRenderer = async (version) => { let ViewUniform = gl.getUniformLocation(program, "view") let ProjectionUniform = gl.getUniformLocation(program, "projection") + gl.cullFace(gl.FRONT) + gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture1); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, texture2); + + // stats.addPanel(new Stats.Panel('FPS', '#0ff', '#002')) document.body.appendChild(stats.dom) renderLoop = (performance) => { @@ -242,28 +258,31 @@ export const initWeblRenderer = async (version) => { gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) view = m4.identity(); - // view = viewer.camera.matrix.elements const yaw = viewer.camera.rotation.x const pitch = viewer.camera.rotation.y m4.rotateX(view, yaw * Math.PI / 180, view) m4.rotateY(view, pitch * Math.PI / 180, view) m4.translate(view, [-viewer.camera.position.x, -viewer.camera.position.y, -viewer.camera.position.z], view) - gl.clearColor(0.1, 0, 0, 0); + gl.clearColor(0.5, 0.5, 0.5, 1.0); gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.DEPTH_BUFFER_BIT) gl.useProgram(program) + + gl.uniformMatrix4fv(ViewUniform, false, view); gl.uniformMatrix4fv(ProjectionUniform, false, projection); gl.bindVertexArray(VAO) + //gl.bindVertexArray(instanceVBO) + gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, NumberOfCube); + //gl.bindVertexArray(null) - - let i = 0 + //let i = 0 // CubePositions = [[ // 2, 90, 2 // ]] @@ -272,28 +291,32 @@ export const initWeblRenderer = async (version) => { // return [...pos.split(',').map(Number), block] as [number, number, number, string] // }) // }).flat() - cubePositions.forEach(([x, y, z, name]) => { - const result = findTextureInBlockStates(name)?.north.texture! - if (result) { - const model = m4.identity() - - //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); - //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) - //m4.rotateZ(model, Math.random() / 1010, model) - m4.translate(model, [x, y, z], model); - gl.uniformMatrix4fv(ModelUniform, false, model); - // const u = 4 * 1 / 64; - // const v = 0 * 1 / 64; - const u = result.u + result.su - const v = result.v - gl.uniform2fv(uvUniform, [u, v]) - - i++ - // i %= 800; - - gl.drawArrays(gl.TRIANGLES, 0, 36); - } - }) + + + // cubePositions.forEach(([x, y, z, name]) => { + // const result = findTextureInBlockStates(name)?.north.texture! + // if (result || true) { + // const model = m4.identity() + + // //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); + // //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) + // //m4.rotateZ(model, Math.random() / 1010, model) + // m4.translate(model, [x, y, z], model); + // gl.uniformMatrix4fv(ModelUniform, false, model); + // const u = i / 64; + // const v = i % 64; + // // const u = result.u + result.su + // // const v = result.v + // gl.uniform2fv(uvUniform, [u, v]) + + // i++ + // i %= 800; + + // gl.drawArrays(gl.TRIANGLES, 0, 36); + // } + // }) + + ///model.translate([0, 0, 0], model) //gl.Swa From a682d4b3da70c64c832d68c14d2858f0536f8310 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 22 Mar 2024 02:33:20 +0300 Subject: [PATCH 13/86] Instanced mashing with indexed texturing --- .../examples/_FragmentShader.frag | 9 ++++--- prismarine-viewer/examples/_VertexShader.vert | 5 ++-- prismarine-viewer/examples/webglRenderer.ts | 27 ++++++++++++++----- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index 0a0bfa7fe..fd2b14d08 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -3,18 +3,19 @@ precision highp float; out vec4 FragColor; in vec2 TexCoord; +in float TextureIndex; uniform sampler2D texture1; uniform sampler2D texture2; -uniform vec2 uv; -uniform vec2 suv; +//uniform vec2 uv; +//uniform vec2 suv; void main() { vec2 position = vec2(1, 1); // I assume gets tile at (1, 1) since the size of the tiles are 1/16 - vec2 size = vec2(1/64, 1/64); - vec2 coord = uv + TexCoord * (1.0f/64.0f); + vec2 size = vec2(1.0/64.0f, 1.0/64.0f); + vec2 coord = size * vec2(int(TextureIndex)%64,int(TextureIndex)/64) + TexCoord * (1.0f/64.0f); FragColor = mix(texture(texture1, coord), texture(texture2, coord), 1.0); } diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index 8bec46229..61ba44cf7 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -2,9 +2,10 @@ layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 aTexCoord; layout (location = 2) in vec3 aOffset; -layout (location = 3) in int TextureIndex; +layout (location = 3) in float aTextureIndex; out vec2 TexCoord; +out float TextureIndex; //uniform mat4 model; uniform mat4 view; @@ -15,5 +16,5 @@ void main() { gl_Position = projection * view * vec4(aPos + aOffset, 1.0f); TexCoord = vec2(aTexCoord.x, (1.0 - aTexCoord.y)); - + TextureIndex = aTextureIndex; } diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 02a2ab9da..b0c9fb0f4 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -74,17 +74,18 @@ export const initWeblRenderer = async (version) => { -0.5, 0.5, -0.5, 0.0, 1.0 // top-let ]) - let NumberOfCube = 25_000 + let NumberOfCube = 1_000_000 let cubePositions = new Float32Array(NumberOfCube * 3) + let cubeTextureIndices = new Float32Array(NumberOfCube); // write random coordinates to cube positions xyz ten cubes; for (let i = 0; i < NumberOfCube * 3; i += 3) { - cubePositions[i] = Math.random() * 100 - 50; - cubePositions[i + 1] = Math.random() * 100 - 50; + cubePositions[i] = Math.random() * 1000 - 500; + cubePositions[i + 1] = Math.random() * 1000 - 500; cubePositions[i + 2] = Math.random() * 100 - 100; - //cubePositions.push([x, y, z, null]); + cubeTextureIndices[i / 3] = Math.floor(Math.random() * 400 + 400); } let instanceVBO = gl.createBuffer(); @@ -92,6 +93,11 @@ export const initWeblRenderer = async (version) => { gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); + let instanceTextureID = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); + gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + let VBO, VAO = gl.createVertexArray(); VBO = gl.createBuffer(); //EBO = gl.createBuffer(); @@ -113,6 +119,12 @@ export const initWeblRenderer = async (version) => { gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(2, 1); + gl.enableVertexAttribArray(3); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); + gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 1 * 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(3, 1); + //gl.bindBuffer(gl.ARRAY_BUFFER, null); //gl.bindVertexArray(null) @@ -148,10 +160,10 @@ export const initWeblRenderer = async (version) => { viewer.camera.position.x += 1 } if (code === 'ShiftLeft') { - viewer.camera.position.y += 0.5 + viewer.camera.position.y -= 0.5 } if (code === 'Space') { - viewer.camera.position.y -= 0.5 + viewer.camera.position.y += 0.5 } } } @@ -264,7 +276,7 @@ export const initWeblRenderer = async (version) => { m4.rotateY(view, pitch * Math.PI / 180, view) m4.translate(view, [-viewer.camera.position.x, -viewer.camera.position.y, -viewer.camera.position.z], view) - gl.clearColor(0.5, 0.5, 0.5, 1.0); + gl.clearColor(0.5, 0.5, 0.5, 0.0); gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.DEPTH_BUFFER_BIT) @@ -278,6 +290,7 @@ export const initWeblRenderer = async (version) => { gl.bindVertexArray(VAO) + //gl.bindVertexArray(instanceVBO) gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, NumberOfCube); //gl.bindVertexArray(null) From b403ed4606d75a554a1b63d682d0c943f67f9b73 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 22 Mar 2024 03:49:23 +0300 Subject: [PATCH 14/86] fix build --- prismarine-viewer/esbuild.mjs | 2 +- prismarine-viewer/examples/_FragmentShader.frag | 2 +- prismarine-viewer/examples/_VertexShader.vert | 2 +- prismarine-viewer/examples/playground.ts | 2 +- prismarine-viewer/examples/webglRenderer.ts | 5 ++++- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/prismarine-viewer/esbuild.mjs b/prismarine-viewer/esbuild.mjs index 4a87d9ff8..de1a3b25f 100644 --- a/prismarine-viewer/esbuild.mjs +++ b/prismarine-viewer/esbuild.mjs @@ -47,7 +47,7 @@ const buildOptions = { http: 'http-browserify', stream: 'stream-browserify', net: 'net-browserify', - 'stats.js': 'node_modules/stats.js/src/Stats.js', + 'stats.js': 'stats.js/src/Stats.js', }, inject: [], metafile: true, diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index fd2b14d08..af598d111 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -3,7 +3,7 @@ precision highp float; out vec4 FragColor; in vec2 TexCoord; -in float TextureIndex; +flat in float TextureIndex; uniform sampler2D texture1; uniform sampler2D texture2; diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index 61ba44cf7..d7e681d2e 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -5,7 +5,7 @@ layout (location = 2) in vec3 aOffset; layout (location = 3) in float aTextureIndex; out vec2 TexCoord; -out float TextureIndex; +flat out float TextureIndex; //uniform mat4 model; uniform mat4 view; diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 51246180a..d8c146176 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -337,7 +337,7 @@ async function main () { update() } applyChanges(true) - gui.openAnimated() + // gui.openAnimated() }) const animate = () => { } diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index b0c9fb0f4..fbc4461ad 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -87,6 +87,9 @@ export const initWeblRenderer = async (version) => { cubePositions[i + 2] = Math.random() * 100 - 100; cubeTextureIndices[i / 3] = Math.floor(Math.random() * 400 + 400); } + cubePositions[0] = 0; + cubePositions[1] = 0; + cubePositions[2] = 0; let instanceVBO = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); @@ -242,7 +245,7 @@ export const initWeblRenderer = async (version) => { document.body.appendChild(canvas) let view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) - const projection = m4.perspective(75 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 512) + const projection = m4.perspective(75 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 2048) // view = m4.identity(); // m4.rotateX(view, yaw * Math.PI / 180) // m4.rotateY(view, pitch * Math.PI / 180) From c6c4ca841b795f49f27a803605ab70903b467fc7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 22 Mar 2024 04:49:33 +0300 Subject: [PATCH 15/86] almost ok for the app --- .../examples/_FragmentShader.frag | 4 +- prismarine-viewer/examples/webglRenderer.ts | 52 +++++++++++++------ prismarine-viewer/viewer/lib/models.ts | 4 +- prismarine-viewer/viewer/lib/worldrenderer.ts | 10 ++-- src/index.ts | 12 ++++- 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index af598d111..18ed49f95 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -14,8 +14,8 @@ uniform sampler2D texture2; void main() { vec2 position = vec2(1, 1); // I assume gets tile at (1, 1) since the size of the tiles are 1/16 - vec2 size = vec2(1.0/64.0f, 1.0/64.0f); - vec2 coord = size * vec2(int(TextureIndex)%64,int(TextureIndex)/64) + TexCoord * (1.0f/64.0f); + vec2 size = vec2(1.0/32.0f, 1.0/32.0f); + vec2 coord = size * vec2(int(TextureIndex)%64,int(TextureIndex)/64) + TexCoord * (1.0f/32.0f); FragColor = mix(texture(texture1, coord), texture(texture2, coord), 1.0); } diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index fbc4461ad..ff486320d 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -18,12 +18,18 @@ declare const viewer: Viewer let renderLoop export const makeRender = () => { - renderLoop() + renderLoop?.() } -export const cubePositions = [] as [number, number, number, string | null][] +let cubePositions +let updateCubes +export const updateCubePositions = () => { + updateCubes() +} + +export const cubePositionsRaw = [] as [number, number, number, string | null][] -export const initWeblRenderer = async (version) => { +export const initWebglRenderer = async (version) => { const stats = new Stats() const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2')! @@ -76,7 +82,7 @@ export const initWeblRenderer = async (version) => { let NumberOfCube = 1_000_000 - let cubePositions = new Float32Array(NumberOfCube * 3) + cubePositions = new Float32Array(NumberOfCube * 3) let cubeTextureIndices = new Float32Array(NumberOfCube); @@ -85,21 +91,40 @@ export const initWeblRenderer = async (version) => { cubePositions[i] = Math.random() * 1000 - 500; cubePositions[i + 1] = Math.random() * 1000 - 500; cubePositions[i + 2] = Math.random() * 100 - 100; - cubeTextureIndices[i / 3] = Math.floor(Math.random() * 400 + 400); + // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 400 + 400); + cubeTextureIndices[i / 3] = 0; } cubePositions[0] = 0; cubePositions[1] = 0; cubePositions[2] = 0; let instanceVBO = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - let instanceTextureID = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); + updateCubes = () => { + cubePositions = new Float32Array(cubePositionsRaw.length * 3) + cubeTextureIndices = new Float32Array(cubePositionsRaw.length); + + cubePositionsRaw.forEach(([x, y, z, name], i) => { + cubePositions[i * 3] = x + cubePositions[i * 3 + 1] = y + cubePositions[i * 3 + 2] = z + // just set index to 0 for now + cubeTextureIndices[i] = 0 + }) + + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); + gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + updateCubes() + globalThis.updateCubes = updateCubes + let VBO, VAO = gl.createVertexArray(); VBO = gl.createBuffer(); @@ -299,9 +324,6 @@ export const initWeblRenderer = async (version) => { //gl.bindVertexArray(null) //let i = 0 - // CubePositions = [[ - // 2, 90, 2 - // ]] // const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { // return Object.entries(chunk.blocks).map(([pos, block]) => { // return [...pos.split(',').map(Number), block] as [number, number, number, string] diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 16ed12041..cbdefdb91 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -418,7 +418,6 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } for (const variant of block.variant) { - console.log(variant) if (!variant || !variant.model) continue if (block.name !== 'water' && block.name !== 'lava'/* && block.isCube */) { @@ -455,7 +454,8 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } } - attr.blocks[`${cursor.x},${cursor.y},${cursor.z}`] = block.name + const pos = block.position + attr.blocks[`${pos.x},${pos.y},${pos.z}`] = block.name } } } diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index 1847a0326..553c33dc8 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -10,7 +10,7 @@ import { toMajor } from './version.js' import PrismarineChatLoader from 'prismarine-chat' import { renderSign } from '../sign-renderer/' import { chunkPos, sectionPos } from './simpleUtils' -import { addCubes, cubePositions } from '../../examples/webglRenderer' +import { cubePositionsRaw, updateCubePositions } from '../../examples/webglRenderer' function mod (x, n) { return ((x % n) + n) % n @@ -90,10 +90,10 @@ export class WorldRenderer { if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return this.newChunks[data.key] = data.geometry - // cubePositions.push(...Object.entries(data.geometry.blocks).map(([pos, block]) => { - // return [...pos.split(',').map(Number), block] as [number, number, number, string] - // })) - + cubePositionsRaw.push(...Object.entries(data.geometry.blocks).map(([pos, block]) => { + return [...pos.split(',').map(Number), block] as [number, number, number, string] + })) + updateCubePositions() // if (!this.initialChunksLoad && this.enableChunksLoadDelay) { // const newPromise = new Promise(resolve => { diff --git a/src/index.ts b/src/index.ts index e10f60a93..3cd6bb758 100644 --- a/src/index.ts +++ b/src/index.ts @@ -96,6 +96,7 @@ import { handleMovementStickDelta, joystickPointer } from './react/TouchAreasCon import { possiblyHandleStateVariable } from './googledrive' import flyingSquidEvents from './flyingSquidEvents' import { hideNotification, notificationProxy } from './react/NotificationProvider' +import { initWebglRenderer } from 'prismarine-viewer/examples/webglRenderer' window.debug = debug window.THREE = THREE @@ -178,11 +179,18 @@ let previousWindowWidth = window.innerWidth let previousWindowHeight = window.innerHeight let max = 0 let rendered = 0 +let windowFocused = true +window.addEventListener('focus', () => { + windowFocused = true +}) +window.addEventListener('blur', () => { + windowFocused = false +}) const renderFrame = (time: DOMHighResTimeStamp) => { if (window.stopLoop) return for (const fn of beforeRenderFrame) fn() window.requestAnimationFrame(renderFrame) - if (window.stopRender || renderer.xr.isPresenting) return + if (window.stopRender || renderer.xr.isPresenting || !windowFocused) return if (renderInterval) { delta += time - lastTime lastTime = time @@ -228,6 +236,7 @@ const resizeHandler = () => { } } +initWebglRenderer('1.14.4') const hud = document.getElementById('hud') const pauseMenu = document.getElementById('pause-screen') @@ -440,6 +449,7 @@ async function connect (connectOptions: { viewer.setVersion(version) } + serverOptions.version = '1.14.4' const downloadVersion = connectOptions.botVersion || (singleplayer ? serverOptions.version : undefined) if (downloadVersion) { await downloadMcData(downloadVersion) From 9e26f70011b28116f2d6af90f6d65e7de489ceed Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 24 Mar 2024 09:29:11 +0300 Subject: [PATCH 16/86] add touch --- prismarine-viewer/examples/TouchControls2.tsx | 63 +++++++++++++++++++ prismarine-viewer/examples/playground.ts | 7 ++- prismarine-viewer/examples/webglRenderer.ts | 28 +++++++-- prismarine-viewer/playground.html | 1 + 4 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 prismarine-viewer/examples/TouchControls2.tsx diff --git a/prismarine-viewer/examples/TouchControls2.tsx b/prismarine-viewer/examples/TouchControls2.tsx new file mode 100644 index 000000000..5d1b4459f --- /dev/null +++ b/prismarine-viewer/examples/TouchControls2.tsx @@ -0,0 +1,63 @@ +import React, { useEffect } from 'react' +import { LeftTouchArea, RightTouchArea, useInterfaceState } from '@dimaka/interface' +import { css } from '@emotion/css' +import { Viewer } from '../viewer/lib/viewer' +import { renderToDom } from '@zardoy/react-util' +import { Vec3 } from 'vec3' +import * as THREE from 'three' + +declare const viewer: Viewer +const Controls = () => { + // todo setting + const usingTouch = navigator.maxTouchPoints > 0 + + useEffect(() => { + let vec3 = new Vec3(0, 0, 0) + + setInterval(() => { + viewer.camera.position.add(new THREE.Vector3(vec3.x, vec3.y, vec3.z)) + }, 1000 / 30) + + useInterfaceState.setState({ + isFlying: false, + uiCustomization: { + touchButtonSize: 40, + }, + updateCoord ([coord, state]) { + vec3 = new Vec3(0, 0, 0) + vec3[coord] = state + } + }) + }, []) + + if (!usingTouch) return null + return ( +
div { + pointer-events: auto; + } + `} + > + +
+ +
+ ) +} + +export const renderPlayground = () => { + renderToDom(, { + // selector: 'body', + }) +} diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index d8c146176..cc6bbfff8 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -14,11 +14,13 @@ import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' // import * as Mathgl from 'math.gl' import { findTextureInBlockStates } from '../../src/playerWindows' -import { initWeblRenderer } from './webglRenderer' +import { initWebglRenderer } from './webglRenderer' +import { renderToDom } from '@zardoy/react-util' globalThis.THREE = THREE //@ts-ignore import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { renderPlayground } from './TouchControls2' const gui = new GUI() @@ -134,7 +136,8 @@ async function main () { viewer.setVersion(version) globalThis.viewer = viewer - await initWeblRenderer(version) + await initWebglRenderer(version) + renderPlayground() // Create viewer diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index ff486320d..afd47ed6e 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -27,7 +27,7 @@ export const updateCubePositions = () => { updateCubes() } -export const cubePositionsRaw = [] as [number, number, number, string | null][] +export let cubePositionsRaw = [] as [number, number, number, string | null][] export const initWebglRenderer = async (version) => { const stats = new Stats() @@ -101,6 +101,16 @@ export const initWebglRenderer = async (version) => { let instanceVBO = gl.createBuffer(); let instanceTextureID = gl.createBuffer(); updateCubes = () => { + return + // cubePositionsRaw = [ + // // for now one cube in front of the camera + // [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z, 'dirt'], + // [viewer.camera.position.x + 2, viewer.camera.position.y, viewer.camera.position.z, 'dirt'], + // [viewer.camera.position.x - 2, viewer.camera.position.y, viewer.camera.position.z, 'dirt'], + // [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z + 2, 'dirt'], + // [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z - 2, 'dirt'], + // ] + cubePositions = new Float32Array(cubePositionsRaw.length * 3) cubeTextureIndices = new Float32Array(cubePositionsRaw.length); @@ -122,7 +132,15 @@ export const initWebglRenderer = async (version) => { gl.bindBuffer(gl.ARRAY_BUFFER, null); } - updateCubes() + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); + gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + // updateCubes() globalThis.updateCubes = updateCubes @@ -200,8 +218,8 @@ export const initWebglRenderer = async (version) => { // mouse const mouse = { x: 0, y: 0 } - const mouseMove = (e) => { - if (e.buttons === 1) { + const mouseMove = (e: PointerEvent) => { + if (e.buttons === 1 || e.pointerType === 'touch') { viewer.camera.rotation.y += e.movementX / 20 viewer.camera.rotation.x += e.movementY / 20 console.log('viewer.camera.position', viewer.camera.position) @@ -212,7 +230,7 @@ export const initWebglRenderer = async (version) => { viewer.camera.position.set(0, 0, 0) } } - window.addEventListener('mousemove', mouseMove) + window.addEventListener('pointermove', mouseMove) viewer.world.texturesVersion = version viewer.world.updateTexturesData() diff --git a/prismarine-viewer/playground.html b/prismarine-viewer/playground.html index fd92009a4..c3902f40d 100644 --- a/prismarine-viewer/playground.html +++ b/prismarine-viewer/playground.html @@ -31,6 +31,7 @@ +
From c8e6475954ed02c8bdd81aa1a8b6d7dda0c84057 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 24 Mar 2024 09:38:46 +0300 Subject: [PATCH 17/86] fix textures --- prismarine-viewer/examples/playground.ts | 2 +- prismarine-viewer/examples/webglRenderer.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index cc6bbfff8..614734b4d 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -68,7 +68,7 @@ async function main () { let continuousRender = false // const { version } = params - const version = '1.18.1' + const version = '1.14.4' // temporary solution until web worker is here, cache data for faster reloads const globalMcData = window['mcData'] if (!globalMcData['version']) { diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index afd47ed6e..3fd306211 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -91,8 +91,8 @@ export const initWebglRenderer = async (version) => { cubePositions[i] = Math.random() * 1000 - 500; cubePositions[i + 1] = Math.random() * 1000 - 500; cubePositions[i + 2] = Math.random() * 100 - 100; - // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 400 + 400); - cubeTextureIndices[i / 3] = 0; + cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); + // cubeTextureIndices[i / 3] = 0; } cubePositions[0] = 0; cubePositions[1] = 0; From 4133eca3a79aee6efe96d63e2d10ec617ac71e3b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 24 Mar 2024 10:44:00 +0300 Subject: [PATCH 18/86] dynamic entrypoint --- esbuild.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esbuild.mjs b/esbuild.mjs index e54623f73..6f7f903ee 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -11,7 +11,9 @@ import { build } from 'esbuild' //@ts-ignore try { await import('./localSettings.mjs') } catch { } -fs.writeFileSync('dist/index.html', fs.readFileSync('index.html', 'utf8').replace('', ''), 'utf8') +const entrypoint = 'index.ts' + +fs.writeFileSync('dist/index.html', fs.readFileSync('index.html', 'utf8').replace('', ``), 'utf8') const watch = process.argv.includes('--watch') || process.argv.includes('-w') const prod = process.argv.includes('--prod') @@ -30,7 +32,7 @@ const buildingVersion = new Date().toISOString().split(':')[0] /** @type {import('esbuild').BuildOptions} */ const buildOptions = { bundle: true, - entryPoints: ['src/index.ts'], + entryPoints: [`src/${entrypoint}`], target: ['es2020'], jsx: 'automatic', jsxDev: dev, From 51ba0eaec573677392e1b8638a9ec246640fd77d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 07:28:03 +0300 Subject: [PATCH 19/86] put renderer into worker! viewerWrapper & perf cypress test --- buildWorkers.mjs | 27 ++ cypress/e2e/index.spec.ts | 18 +- cypress/e2e/performance.spec.ts | 25 + cypress/e2e/shared.ts | 15 + package.json | 1 + .../examples/_FragmentShader.frag | 2 +- prismarine-viewer/examples/playground.ts | 54 +++ prismarine-viewer/examples/webglRenderer.ts | 456 +++--------------- .../examples/webglRendererWorker.ts | 404 ++++++++++++++++ prismarine-viewer/viewer/lib/viewerWrapper.ts | 117 +++++ src/index.ts | 71 +-- 11 files changed, 707 insertions(+), 483 deletions(-) create mode 100644 buildWorkers.mjs create mode 100644 cypress/e2e/performance.spec.ts create mode 100644 cypress/e2e/shared.ts create mode 100644 prismarine-viewer/examples/webglRendererWorker.ts create mode 100644 prismarine-viewer/viewer/lib/viewerWrapper.ts diff --git a/buildWorkers.mjs b/buildWorkers.mjs new file mode 100644 index 000000000..9807c4593 --- /dev/null +++ b/buildWorkers.mjs @@ -0,0 +1,27 @@ +// main worker file intended for computing world geometry is built using prismarine-viewer/buildWorker.mjs +import { build, context } from 'esbuild' + +const watch = process.argv.includes('-w') + +const result = await (watch ? context : build)({ + bundle: true, + platform: 'browser', + entryPoints: ['prismarine-viewer/examples/webglRendererWorker.ts'], + outfile: 'prismarine-viewer/public/webglRendererWorker.js', + sourcemap: 'inline', + // minify: true, + logLevel: 'info', + plugins: [], + loader: { + '.vert': 'text', + '.frag': 'text' + }, + mainFields: [ + 'browser', 'module', 'main' + ], + keepNames: true, +}) + +if (watch) { + await result.watch() +} diff --git a/cypress/e2e/index.spec.ts b/cypress/e2e/index.spec.ts index 8b168bf12..e9d97ab06 100644 --- a/cypress/e2e/index.spec.ts +++ b/cypress/e2e/index.spec.ts @@ -1,15 +1,5 @@ /// -import type { AppOptions } from '../../src/optionsStorage' - -const cleanVisit = (url?) => { - cy.clearLocalStorage() - visit(url) -} - -const visit = (url = '/') => { - window.localStorage.cypress = 'true' - cy.visit(url) -} +import { setOptions, cleanVisit, visit } from './shared' // todo use ssl @@ -31,12 +21,6 @@ const testWorldLoad = () => { }) } -const setOptions = (options: Partial) => { - cy.window().then(win => { - Object.assign(win['options'], options) - }) -} - it('Loads & renders singleplayer', () => { cleanVisit('/?singleplayer=1') setOptions({ diff --git a/cypress/e2e/performance.spec.ts b/cypress/e2e/performance.spec.ts new file mode 100644 index 000000000..f2fc4d46e --- /dev/null +++ b/cypress/e2e/performance.spec.ts @@ -0,0 +1,25 @@ +import { cleanVisit, setOptions } from './shared' + +it('Loads & renders singleplayer', () => { + cleanVisit('/?singleplayer=1') + setOptions({ + renderDistance: 2 + }) + // wait for .initial-loader to disappear + cy.get('.initial-loader', { timeout: 20_000 }).should('not.exist') + cy.window() + .its('performance') + .invoke('mark', 'worldLoad') + + cy.document().then({ timeout: 20_000 }, doc => { + return new Cypress.Promise(resolve => { + doc.addEventListener('cypress-world-ready', resolve) + }) + }).then(() => { + const duration = cy.window() + .its('performance') + .invoke('measure', 'modalOpen') + .its('duration') + cy.log('Duration', duration) + }) +}) diff --git a/cypress/e2e/shared.ts b/cypress/e2e/shared.ts new file mode 100644 index 000000000..c445b7df6 --- /dev/null +++ b/cypress/e2e/shared.ts @@ -0,0 +1,15 @@ +import { AppOptions } from '../../src/optionsStorage' + +export const cleanVisit = (url?) => { + cy.clearLocalStorage() + visit(url) +} +export const visit = (url = '/') => { + window.localStorage.cypress = 'true' + cy.visit(url) +} +export const setOptions = (options: Partial) => { + cy.window().then(win => { + Object.assign(win['options'], options) + }) +} diff --git a/package.json b/package.json index 27e0d4d91..98f6727b5 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod", "check-build": "tsc && pnpm test-unit && pnpm build", "test:cypress": "cypress run", + "test:cypress:perf": "cypress run --spec cypress/e2e/perf.spec.ts --browser edge", "test-unit": "vitest", "test:e2e": "start-test http-get://localhost:8080 test:cypress", "prod-start": "node server.js", diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index 18ed49f95..f5a07f55a 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -15,7 +15,7 @@ void main() { vec2 position = vec2(1, 1); // I assume gets tile at (1, 1) since the size of the tiles are 1/16 vec2 size = vec2(1.0/32.0f, 1.0/32.0f); - vec2 coord = size * vec2(int(TextureIndex)%64,int(TextureIndex)/64) + TexCoord * (1.0f/32.0f); + vec2 coord = size * vec2(int(TextureIndex)%32,int(TextureIndex)/32) + TexCoord * (1.0f/32.0f); FragColor = mix(texture(texture1, coord), texture(texture2, coord), 1.0); } diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 614734b4d..760802f01 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -135,9 +135,63 @@ async function main () { const viewer = new Viewer(nullRenderer, 1) viewer.setVersion(version) globalThis.viewer = viewer + await new Promise(resolve => { + // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) + // viewer.world.material.map!.image.onload = () => { + // console.log(this.material.map!.image) + // resolve() + // } + viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) + }) await initWebglRenderer(version) + const simpleControls = () => { + const keys = (e) => { + const code = e.code + const pressed = e.type === 'keydown' + if (pressed) { + if (code === 'KeyW') { + viewer.camera.position.z -= 1 + } + if (code === 'KeyS') { + viewer.camera.position.z += 1 + } + if (code === 'KeyA') { + viewer.camera.position.x -= 1 + } + if (code === 'KeyD') { + viewer.camera.position.x += 1 + } + if (code === 'ShiftLeft') { + viewer.camera.position.y -= 0.5 + } + if (code === 'Space') { + viewer.camera.position.y += 0.5 + } + } + } + window.addEventListener('keydown', keys) + window.addEventListener('keyup', keys) + + // mouse + const mouse = { x: 0, y: 0 } + const mouseMove = (e: PointerEvent) => { + if (e.buttons === 1 || e.pointerType === 'touch') { + viewer.camera.rotation.y += e.movementX / 20 + viewer.camera.rotation.x += e.movementY / 20 + console.log('viewer.camera.position', viewer.camera.position) + // yaw += e.movementY / 20; + // pitch += e.movementX / 20; + } + if (e.buttons === 2) { + viewer.camera.position.set(0, 0, 0) + } + } + window.addEventListener('pointermove', mouseMove) + } + simpleControls() renderPlayground() + return // Create viewer diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 3fd306211..217e852a8 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -1,25 +1,10 @@ -import * as THREE from 'three' -import { m4 } from 'twgl.js' -import Stats from 'stats.js' - -//@ts-ignore -import VertShader from './_VertexShader.vert' -//@ts-ignore -import FragShader from './_FragmentShader.frag' - -//@ts-ignore -import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' -//@ts-ignore -import Stone from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/stone.png' import { Viewer } from '../viewer/lib/viewer' -import { findTextureInBlockStates } from '../../src/playerWindows' + +let worker declare const viewer: Viewer -let renderLoop -export const makeRender = () => { - renderLoop?.() -} +export const makeRender = () => { } let cubePositions let updateCubes @@ -29,390 +14,69 @@ export const updateCubePositions = () => { export let cubePositionsRaw = [] as [number, number, number, string | null][] -export const initWebglRenderer = async (version) => { - const stats = new Stats() - const canvas = document.createElement('canvas') - const gl = canvas.getContext('webgl2')! - - const program = createProgram(gl, VertShader, FragShader) - - let vertices = new Float32Array([ - -0.5, -0.5, -0.5, 0.0, 0.0, // Bottom-let - 0.5, -0.5, -0.5, 1.0, 0.0, // bottom-right - 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - -0.5, 0.5, -0.5, 0.0, 1.0, // top-let - -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let - // ront ace - -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let - 0.5, 0.5, 0.5, 1.0, 1.0, // top-right - 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-right - 0.5, 0.5, 0.5, 1.0, 1.0, // top-right - -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let - -0.5, 0.5, 0.5, 0.0, 1.0, // top-let - // Let ace - -0.5, 0.5, 0.5, 1.0, 0.0, // top-right - -0.5, -0.5, -0.5, 0.0, 1.0, // bottom-let - -0.5, 0.5, -0.5, 1.0, 1.0, // top-let - -0.5, -0.5, -0.5, 0.0, 1.0, // bottom-let - -0.5, 0.5, 0.5, 1.0, 0.0, // top-right - -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-right - // Right ace - 0.5, 0.5, 0.5, 1.0, 0.0, // top-let - 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - 0.5, -0.5, -0.5, 0.0, 1.0, // bottom-right - 0.5, -0.5, -0.5, 0.0, 1.0, // bottom-right - 0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let - 0.5, 0.5, 0.5, 1.0, 0.0, // top-let - // Bottom ace - -0.5, -0.5, -0.5, 0.0, 1.0, // top-right - 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-let - 0.5, -0.5, -0.5, 1.0, 1.0, // top-let - 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-let - -0.5, -0.5, -0.5, 0.0, 1.0, // top-right - -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-right - // Top ace - -0.5, 0.5, -0.5, 0.0, 1.0, // top-let - 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - 0.5, 0.5, 0.5, 1.0, 0.0, // bottom-right - 0.5, 0.5, 0.5, 1.0, 0.0, // bottom-right - -0.5, 0.5, 0.5, 0.0, 0.0, // bottom-let - -0.5, 0.5, -0.5, 0.0, 1.0 // top-let - ]) - - let NumberOfCube = 1_000_000 - - cubePositions = new Float32Array(NumberOfCube * 3) - let cubeTextureIndices = new Float32Array(NumberOfCube); - - - // write random coordinates to cube positions xyz ten cubes; - for (let i = 0; i < NumberOfCube * 3; i += 3) { - cubePositions[i] = Math.random() * 1000 - 500; - cubePositions[i + 1] = Math.random() * 1000 - 500; - cubePositions[i + 2] = Math.random() * 100 - 100; - cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); - // cubeTextureIndices[i / 3] = 0; - } - cubePositions[0] = 0; - cubePositions[1] = 0; - cubePositions[2] = 0; - - let instanceVBO = gl.createBuffer(); - let instanceTextureID = gl.createBuffer(); - updateCubes = () => { - return - // cubePositionsRaw = [ - // // for now one cube in front of the camera - // [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z, 'dirt'], - // [viewer.camera.position.x + 2, viewer.camera.position.y, viewer.camera.position.z, 'dirt'], - // [viewer.camera.position.x - 2, viewer.camera.position.y, viewer.camera.position.z, 'dirt'], - // [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z + 2, 'dirt'], - // [viewer.camera.position.x, viewer.camera.position.y, viewer.camera.position.z - 2, 'dirt'], - // ] - - cubePositions = new Float32Array(cubePositionsRaw.length * 3) - cubeTextureIndices = new Float32Array(cubePositionsRaw.length); - - cubePositionsRaw.forEach(([x, y, z, name], i) => { - cubePositions[i * 3] = x - cubePositions[i * 3 + 1] = y - cubePositions[i * 3 + 2] = z - // just set index to 0 for now - cubeTextureIndices[i] = 0 - }) - - - gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); +const sendWorkerMessage = (message: any, transfer?: Transferable[]) => { + worker.postMessage(message, transfer) + // replacable by onmessage +} - gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); +export const initWebglRenderer = async (version: string | undefined) => { + const imageBlob = await fetch(`./textures/${version}.png`).then((res) => res.blob()) + const canvas = document.createElement('canvas') + canvas.width = window.innerWidth * window.devicePixelRatio + canvas.height = window.innerHeight * window.devicePixelRatio + document.body.appendChild(canvas) + canvas.id = 'viewer-canvas' + const offscreen = canvas.transferControlToOffscreen() + + // replacable by initWebglRenderer + worker = new Worker('./webglRendererWorker.js') + sendWorkerMessage({ + canvas: offscreen, + imageBlob, + blockStatesJson: viewer.world.downloadedBlockStatesData + }, [offscreen]) + + let oldWidth = window.innerWidth + let oldHeight = window.innerHeight + let oldCamera = { + position: { x: 0, y: 0, z: 0 }, + rotation: { x: 0, y: 0, z: 0 } } - - gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - // updateCubes() - globalThis.updateCubes = updateCubes - - - let VBO, VAO = gl.createVertexArray(); - VBO = gl.createBuffer(); - //EBO = gl.createBuffer(); - - gl.bindVertexArray(VAO); - gl.bindBuffer(gl.ARRAY_BUFFER, VBO) - gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) - - gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) - gl.enableVertexAttribArray(0) - - gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) - gl.enableVertexAttribArray(1) - //instance data - - gl.enableVertexAttribArray(2); - gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 3 * 4, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.vertexAttribDivisor(2, 1); - - gl.enableVertexAttribArray(3); - gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 1 * 4, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.vertexAttribDivisor(3, 1); - - //gl.bindBuffer(gl.ARRAY_BUFFER, null); - //gl.bindVertexArray(null) - - let image = new Image(); - // simple black white chess image 10x10 - image.src = Dirt - let image2 = new Image(); - // simple black white chess image 10x10 - image2.src = `/textures/${version}.png` - - console.log(image.src) - await new Promise((resolve) => { - image.onload = resolve + let focused = true + window.addEventListener('focus', () => { + focused = true + sendWorkerMessage({ type: 'startRender' }) }) - await new Promise((resolve) => { - image2.onload = resolve + window.addEventListener('blur', () => { + focused = false + sendWorkerMessage({ type: 'stopRender' }) }) - - const keys = (e) => { - const code = e.code - const pressed = e.type === 'keydown' - if (pressed) { - if (code === 'KeyW') { - viewer.camera.position.z -= 1 - } - if (code === 'KeyS') { - viewer.camera.position.z += 1 - } - if (code === 'KeyA') { - viewer.camera.position.x -= 1 - } - if (code === 'KeyD') { - viewer.camera.position.x += 1 - } - if (code === 'ShiftLeft') { - viewer.camera.position.y -= 0.5 - } - if (code === 'Space') { - viewer.camera.position.y += 0.5 - } - } - } - window.addEventListener('keydown', keys) - window.addEventListener('keyup', keys) - - // mouse - const mouse = { x: 0, y: 0 } - const mouseMove = (e: PointerEvent) => { - if (e.buttons === 1 || e.pointerType === 'touch') { - viewer.camera.rotation.y += e.movementX / 20 - viewer.camera.rotation.x += e.movementY / 20 - console.log('viewer.camera.position', viewer.camera.position) - // yaw += e.movementY / 20; - // pitch += e.movementX / 20; + const mainLoop = () => { + requestAnimationFrame(mainLoop) + if (!focused) return + + if (oldWidth !== window.innerWidth || oldHeight !== window.innerHeight) { + oldWidth = window.innerWidth + oldHeight = window.innerHeight + sendWorkerMessage({ + type: 'resize', + newWidth: window.innerWidth * window.devicePixelRatio, + height: window.innerHeight * window.devicePixelRatio + }) } - if (e.buttons === 2) { - viewer.camera.position.set(0, 0, 0) - } - } - window.addEventListener('pointermove', mouseMove) - - viewer.world.texturesVersion = version - viewer.world.updateTexturesData() - await new Promise(resolve => { - // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) - // viewer.world.material.map!.image.onload = () => { - // console.log(this.material.map!.image) - // resolve() - // } - viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) - }) - console.log(viewer.world.downloadedBlockStatesData) - const names = Object.keys(viewer.world.downloadedBlockStatesData) - - let texture1 = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture1); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image.width, image.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image); - //gl.generateMipmap(gl.TEXTURE_2D); - - let texture2 = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture2); - - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, image2.width, image2.height, 0, gl.RGB, gl.UNSIGNED_BYTE, image2); - //gl.generateMipmap(gl.TEXTURE_2D); - - gl.useProgram(program) - - - - gl.uniform1i(gl.getUniformLocation(program, "texture1"), 0); - gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); - - - gl.enable(gl.DEPTH_TEST) - gl.frontFace(gl.CCW) - gl.enable(gl.CULL_FACE) - - - //gl.generateMipmap() - //gl.enable(gl) - //gl.clearColor(0, 0, 0, 1) - //gl.clear(gl.COLOR_BUFFER_BIT) - document.body.appendChild(canvas) - - let view = m4.lookAt([0, 0, 2], [0, 0, 0], [0, 1, 0]) - const projection = m4.perspective(75 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 2048) - // view = m4.identity(); - // m4.rotateX(view, yaw * Math.PI / 180) - // m4.rotateY(view, pitch * Math.PI / 180) - // m4.translate(view, [x,y,z], view) - let ModelUniform = gl.getUniformLocation(program, "model") - let uvUniform = gl.getUniformLocation(program, "uv"); - let ViewUniform = gl.getUniformLocation(program, "view") - let ProjectionUniform = gl.getUniformLocation(program, "projection") - - gl.cullFace(gl.FRONT) - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture1); - gl.activeTexture(gl.TEXTURE1); - gl.bindTexture(gl.TEXTURE_2D, texture2); - - - - // stats.addPanel(new Stats.Panel('FPS', '#0ff', '#002')) - document.body.appendChild(stats.dom) - renderLoop = (performance) => { - stats.begin() - gl.canvas.width = window.innerWidth * window.devicePixelRatio - gl.canvas.height = window.innerHeight * window.devicePixelRatio - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) - - view = m4.identity(); - const yaw = viewer.camera.rotation.x - const pitch = viewer.camera.rotation.y - m4.rotateX(view, yaw * Math.PI / 180, view) - m4.rotateY(view, pitch * Math.PI / 180, view) - m4.translate(view, [-viewer.camera.position.x, -viewer.camera.position.y, -viewer.camera.position.z], view) - - gl.clearColor(0.5, 0.5, 0.5, 0.0); - gl.clear(gl.COLOR_BUFFER_BIT) - gl.clear(gl.DEPTH_BUFFER_BIT) - - gl.useProgram(program) - - - - gl.uniformMatrix4fv(ViewUniform, false, view); - gl.uniformMatrix4fv(ProjectionUniform, false, projection); - - - - gl.bindVertexArray(VAO) - - //gl.bindVertexArray(instanceVBO) - gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, NumberOfCube); - //gl.bindVertexArray(null) - - //let i = 0 - // const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { - // return Object.entries(chunk.blocks).map(([pos, block]) => { - // return [...pos.split(',').map(Number), block] as [number, number, number, string] - // }) - // }).flat() - - - // cubePositions.forEach(([x, y, z, name]) => { - // const result = findTextureInBlockStates(name)?.north.texture! - // if (result || true) { - // const model = m4.identity() - - // //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); - // //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) - // //m4.rotateZ(model, Math.random() / 1010, model) - // m4.translate(model, [x, y, z], model); - // gl.uniformMatrix4fv(ModelUniform, false, model); - // const u = i / 64; - // const v = i % 64; - // // const u = result.u + result.su - // // const v = result.v - // gl.uniform2fv(uvUniform, [u, v]) - - // i++ - // i %= 800; - - // gl.drawArrays(gl.TRIANGLES, 0, 36); - // } - // }) - - - ///model.translate([0, 0, 0], model) - - //gl.Swa - stats.end() - } - - // gl.deleteVertexArray(VAO); - // gl.deleteBuffer(VBO) - // gl.deleteBuffer(EBO) - // gl.deleteProgram(program) - -} - -export const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmentShader: string) => { - const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => { - const shaderName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment' - const shader = gl.createShader(type)! - gl.shaderSource(shader, source) - gl.compileShader(shader) - - const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) - if (!success) { - const info = gl.getShaderInfoLog(shader) - gl.deleteShader(shader) - throw new Error(`Shader ${shaderName} compile error: ` + info) + if (['rotation', 'position'].some((key) => oldCamera[key] !== viewer.camera[key])) { + // TODO fix + for (const [key, val] of Object.entries(oldCamera)) { + for (const key2 of Object.keys(val)) { + oldCamera[key][key2] = viewer.camera[key][key2] + } + } + sendWorkerMessage({ + type: 'camera', + camera: oldCamera + }) } - return shader } - - - const program = gl.createProgram()! - gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) - gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)!) - gl.linkProgram(program) - const linkSuccess = gl.getProgramParameter(program, gl.LINK_STATUS) - if (!linkSuccess) { - const info = gl.getProgramInfoLog(program) - gl.deleteProgram(program) - throw new Error('Program link error: ' + info) - } - return program + requestAnimationFrame(mainLoop) } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts new file mode 100644 index 000000000..e634d6fa7 --- /dev/null +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -0,0 +1,404 @@ +import * as THREE from 'three' + +//@ts-ignore +import VertShader from './_VertexShader.vert' +//@ts-ignore +import FragShader from './_FragmentShader.frag' + +let blockStates + +const findTextureInBlockStates = (name): any => { + // assertDefined(viewer) + // const blockStates: BlockStates = viewer.world.customBlockStatesData || viewer.world.downloadedBlockStatesData + // const vars = blockStates[name]?.variants + // if (!vars) return + // let firstVar = Object.values(vars)[0] + // if (Array.isArray(firstVar)) firstVar = firstVar[0] + // if (!firstVar) return + // const elements = firstVar.model?.elements + // if (elements?.length !== 1) return + // return elements[0].faces +} + +let cubePositionsRaw = [] as [number, number, number, string | null][] + +const camera = new THREE.PerspectiveCamera(75, 1 / 1, 0.1, 1000) + +const updateSize = (width, height) => { + camera.aspect = width / height + camera.updateProjectionMatrix() +} + +let renderedFrames = 0 +setInterval(() => { + console.log('FPS:', renderedFrames) + renderedFrames = 0 +}, 1000) + +let rendering = true +let cubePositions +let updateCubes +const updateCubePositions = () => { + updateCubes() +} + +export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: ImageBitmapSource, blockStatesJson: any) => { + const textureBitmap = await createImageBitmap(imageBlob) + const textureWidth = textureBitmap.width + const textureHeight = textureBitmap.height + + const gl = canvas.getContext('webgl2')! + + const program = createProgram(gl, VertShader, FragShader) + + let vertices = new Float32Array([ + -0.5, -0.5, -0.5, 0.0, 0.0, // Bottom-let + 0.5, -0.5, -0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + -0.5, 0.5, -0.5, 0.0, 1.0, // top-let + -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let + // ront ace + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let + 0.5, 0.5, 0.5, 1.0, 1.0, // top-right + 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, 0.5, 1.0, 1.0, // top-right + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let + -0.5, 0.5, 0.5, 0.0, 1.0, // top-let + // Let ace + -0.5, 0.5, 0.5, 1.0, 0.0, // top-right + -0.5, -0.5, -0.5, 0.0, 1.0, // bottom-let + -0.5, 0.5, -0.5, 1.0, 1.0, // top-let + -0.5, -0.5, -0.5, 0.0, 1.0, // bottom-let + -0.5, 0.5, 0.5, 1.0, 0.0, // top-right + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-right + // Right ace + 0.5, 0.5, 0.5, 1.0, 0.0, // top-let + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, -0.5, -0.5, 0.0, 1.0, // bottom-right + 0.5, -0.5, -0.5, 0.0, 1.0, // bottom-right + 0.5, -0.5, 0.5, 0.0, 0.0, // bottom-let + 0.5, 0.5, 0.5, 1.0, 0.0, // top-let + // Bottom ace + -0.5, -0.5, -0.5, 0.0, 1.0, // top-right + 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-let + 0.5, -0.5, -0.5, 1.0, 1.0, // top-let + 0.5, -0.5, 0.5, 1.0, 0.0, // bottom-let + -0.5, -0.5, -0.5, 0.0, 1.0, // top-right + -0.5, -0.5, 0.5, 0.0, 0.0, // bottom-right + // Top ace + -0.5, 0.5, -0.5, 0.0, 1.0, // top-let + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, 0.5, 0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, 0.5, 1.0, 0.0, // bottom-right + -0.5, 0.5, 0.5, 0.0, 0.0, // bottom-let + -0.5, 0.5, -0.5, 0.0, 1.0 // top-let + ]) + + let NumberOfCube = 1_000_000 + + cubePositions = new Float32Array(NumberOfCube * 3) + let cubeTextureIndices = new Float32Array(NumberOfCube); + + + // write random coordinates to cube positions xyz ten cubes; + for (let i = 0; i < NumberOfCube * 3; i += 3) { + cubePositions[i] = Math.random() * 1000 - 500; + cubePositions[i + 1] = Math.random() * 1000 - 500; + cubePositions[i + 2] = Math.random() * 100 - 100; + cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); + // cubeTextureIndices[i / 3] = 0; + } + cubePositions[0] = 0; + cubePositions[1] = 0; + cubePositions[2] = 0; + + let VAO = gl.createVertexArray(); + updateCubes = () => { + // cubePositionsRaw = [ + // // for now one cube in front of the camera + // [camera.position.x, camera.position.y, camera.position.z, 'dirt'], + // [camera.position.x + 2, camera.position.y, camera.position.z, 'dirt'], + // [camera.position.x - 2, camera.position.y, camera.position.z, 'dirt'], + // [camera.position.x, camera.position.y, camera.position.z + 2, 'dirt'], + // [camera.position.x, camera.position.y, camera.position.z - 2, 'dirt'], + // ] + // NumberOfCube = cubePositionsRaw.length + // cubePositions = new Float32Array(NumberOfCube * 3) + // cubeTextureIndices = new Float32Array(NumberOfCube); + // for (let i = 0; i < NumberOfCube * 3; i += 3) { + // cubePositions[i] = cubePositionsRaw[i / 3][0] + // cubePositions[i + 1] = cubePositionsRaw[i / 3][1] + // cubePositions[i + 2] = cubePositionsRaw[i / 3][2] + // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); + // const name = cubePositionsRaw[i / 3][3] + // const result = findTextureInBlockStates(name)?.north.texture! ?? findTextureInBlockStates('sponge')?.north.texture! + // const tileSize = 16; + // function uvToTextureIndex (u, v) { + // // Convert pixel coordinates to tile index + // const tileX = Math.floor(u * textureWidth / tileSize); + // const tileY = Math.floor(v * textureHeight / tileSize); + + // // Calculate texture index + // const textureIndex = tileY * (textureWidth / tileSize) + tileX; + + // return textureIndex; + // } + // cubeTextureIndices[i / 3] = uvToTextureIndex(result.u, result.v) + // } + + let instanceVBO = gl.createBuffer(); + let instanceTextureID = gl.createBuffer(); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); + gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + VAO = gl.createVertexArray(); + let VBO = gl.createBuffer(); + //EBO = gl.createBuffer(); + + gl.bindVertexArray(VAO); + gl.bindBuffer(gl.ARRAY_BUFFER, VBO) + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) + + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) + gl.enableVertexAttribArray(0) + + gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) + gl.enableVertexAttribArray(1) + //instance data + + gl.enableVertexAttribArray(2); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 3 * 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(2, 1); + + gl.enableVertexAttribArray(3); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); + gl.vertexAttribPointer(3, 1, gl.FLOAT, false, 1 * 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(3, 1); + } + + updateCubes() + globalThis.updateCubes = updateCubes + + + + //gl.bindBuffer(gl.ARRAY_BUFFER, null); + //gl.bindVertexArray(null) + + // viewer.world.updateTexturesData() + // await new Promise(resolve => { + // // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) + // // viewer.world.material.map!.image.onload = () => { + // // console.log(this.material.map!.image) + // // resolve() + // // } + // viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) + // }) + // const names = Object.keys(viewer.world.downloadedBlockStatesData) + + let texture1 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture1); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, textureWidth, textureHeight, 0, gl.RGB, gl.UNSIGNED_BYTE, textureBitmap); + //gl.generateMipmap(gl.TEXTURE_2D); + + const texture2 = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture2); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, textureWidth, textureHeight, 0, gl.RGB, gl.UNSIGNED_BYTE, textureBitmap); + //gl.generateMipmap(gl.TEXTURE_2D); + + gl.useProgram(program) + + + + gl.uniform1i(gl.getUniformLocation(program, "texture1"), 0); + gl.uniform1i(gl.getUniformLocation(program, "texture2"), 1); + + + gl.enable(gl.DEPTH_TEST) + gl.frontFace(gl.CCW) + gl.enable(gl.CULL_FACE) + + + //gl.generateMipmap() + //gl.enable(gl) + //gl.clearColor(0, 0, 0, 1) + //gl.clear(gl.COLOR_BUFFER_BIT) + + let ViewUniform = gl.getUniformLocation(program, "view") + let ProjectionUniform = gl.getUniformLocation(program, "projection") + + gl.cullFace(gl.FRONT) + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture1); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, texture2); + + updateSize(gl.canvas.width, gl.canvas.height) + const renderLoop = (performance) => { + // gl.canvas.width = window.innerWidth * window.devicePixelRatio + // gl.canvas.height = window.innerHeight * window.devicePixelRatio + if (newWidth || newHeight) { + gl.canvas.width = newWidth ?? gl.canvas.width + gl.canvas.height = newHeight ?? gl.canvas.height + newWidth = undefined + newHeight = undefined + updateSize(gl.canvas.width, gl.canvas.height) + } + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) + + let view = new THREE.Matrix4(); + // Rotate the view matrix around the X axis by yaw (in radians) + const yaw = camera.rotation.x + const pitch = camera.rotation.y + view.makeRotationX(yaw * Math.PI / 4); + // Rotate the view matrix around the Y axis by pitch (in radians) + view.multiply(new THREE.Matrix4().makeRotationY(pitch * Math.PI / 180)); + // Translate the view matrix by the vector [x, y, z] + view.multiply(new THREE.Matrix4().makeTranslation(camera.position.x, camera.position.y, camera.position.z)); + + gl.clearColor(0.5, 0.5, 0.5, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT) + gl.clear(gl.DEPTH_BUFFER_BIT) + + gl.useProgram(program) + + + camera.updateMatrix() + camera.updateProjectionMatrix() + gl.uniformMatrix4fv(ViewUniform, false, view.elements); + gl.uniformMatrix4fv(ProjectionUniform, false, camera.projectionMatrix.elements); + + + + gl.bindVertexArray(VAO) + + //gl.bindVertexArray(instanceVBO) + gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, NumberOfCube); + //gl.bindVertexArray(null) + + //let i = 0 + // const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { + // return Object.entries(chunk.blocks).map(([pos, block]) => { + // return [...pos.split(',').map(Number), block] as [number, number, number, string] + // }) + // }).flat() + + + // cubePositions.forEach(([x, y, z, name]) => { + // if (result || true) { + // const model = m4.identity() + + // //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); + // //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) + // //m4.rotateZ(model, Math.random() / 1010, model) + // m4.translate(model, [x, y, z], model); + // gl.uniformMatrix4fv(ModelUniform, false, model); + // const u = i / 64; + // const v = i % 64; + // // const u = result.u + result.su + // // const v = result.v + // gl.uniform2fv(uvUniform, [u, v]) + + // i++ + // i %= 800; + + // gl.drawArrays(gl.TRIANGLES, 0, 36); + // } + // }) + + + ///model.translate([0, 0, 0], model) + + renderedFrames++ + requestAnimationFrame(renderLoop) + } + requestAnimationFrame(renderLoop) + + // gl.deleteVertexArray(VAO); + // gl.deleteBuffer(VBO) + // gl.deleteBuffer(EBO) + // gl.deleteProgram(program) + + return canvas +} + +const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmentShader: string) => { + const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => { + const shaderName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment' + const shader = gl.createShader(type)! + gl.shaderSource(shader, source) + gl.compileShader(shader) + + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) + if (!success) { + const info = gl.getShaderInfoLog(shader) + gl.deleteShader(shader) + throw new Error(`Shader ${shaderName} compile error: ` + info) + } + return shader + } + + + + const program = gl.createProgram()! + gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)!) + gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)!) + gl.linkProgram(program) + const linkSuccess = gl.getProgramParameter(program, gl.LINK_STATUS) + if (!linkSuccess) { + const info = gl.getProgramInfoLog(program) + gl.deleteProgram(program) + throw new Error('Program link error: ' + info) + } + return program +} + +let started = false +let newWidth: number | undefined +let newHeight: number | undefined +onmessage = function (e) { + if (!started) { + started = true + initWebglRenderer(e.data.canvas, e.data.imageBlob, e.data.blockStatesJson) + return + } + if (e.data.type === 'startRender') { + rendering = true + } + if (e.data.type === 'stopRender') { + rendering = false + } + if (e.data.type === 'resize') { + newWidth = e.data.newWidth + newHeight = e.data.newHeight + } + if (e.data.cubePositionsRaw) { + cubePositionsRaw = e.data.cubePositionsRaw + } + if (e.data.type === 'camera') { + camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) + camera.rotation.set(e.data.camera.rotation.x, e.data.camera.rotation.y, e.data.camera.rotation.z, 'ZYX') + } +} diff --git a/prismarine-viewer/viewer/lib/viewerWrapper.ts b/prismarine-viewer/viewer/lib/viewerWrapper.ts new file mode 100644 index 000000000..7e489a6c5 --- /dev/null +++ b/prismarine-viewer/viewer/lib/viewerWrapper.ts @@ -0,0 +1,117 @@ +// wrapper for now +export class ViewerWrapper { + previousWindowWidth: number + previousWindowHeight: number + globalObject = globalThis as any + stopRenderOnBlur = true + addedToPage = false + renderInterval = 0 + fpsInterval + + constructor(public canvas: HTMLCanvasElement, public renderer?: THREE.WebGLRenderer) { + } + addToPage (startRendering = true) { + if (this.addedToPage) throw new Error('Already added to page') + let pixelRatio = window.devicePixelRatio || 1 // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable + if (this.renderer) { + if (!this.renderer.capabilities.isWebGL2) pixelRatio = 1 // webgl1 has issues with high pixel ratio (sometimes screen is clipped) + this.renderer.setPixelRatio(pixelRatio) + this.renderer.setSize(window.innerWidth, window.innerHeight) + } else { + this.canvas.width = window.innerWidth * pixelRatio + this.canvas.height = window.innerHeight * pixelRatio + } + this.previousWindowWidth = window.innerWidth + this.previousWindowHeight = window.innerHeight + + this.canvas.id = 'viewer-canvas' + document.body.appendChild(this.canvas) + + if (this.renderer) this.globalObject.renderer = this.renderer + this.addedToPage = true + + let max = 0 + this.fpsInterval = setInterval(() => { + if (max > 0) { + viewer.world.droppedFpsPercentage = this.renderedFps / max + } + max = Math.max(this.renderedFps, max) + this.renderedFps = 0 + }, 1000) + if (startRendering) { + this.globalObject.requestAnimationFrame(this.render.bind(this)) + } + if (typeof window !== 'undefined') { + this.trackWindowFocus() + } + } + + windowFocused = true + trackWindowFocus () { + window.addEventListener('focus', () => { + this.windowFocused = true + }) + window.addEventListener('blur', () => { + this.windowFocused = false + }) + } + + dispose () { + if (!this.addedToPage) throw new Error('Not added to page') + document.body.removeChild(this.canvas) + this.renderer?.dispose() + // this.addedToPage = false + clearInterval(this.fpsInterval) + } + + + renderedFps = 0 + lastTime = performance.now() + delta = 0 + startRender = () => { } + endRender = () => { } + render (time: DOMHighResTimeStamp) { + if (this.globalObject.stopLoop) return + for (const fn of beforeRenderFrame) fn() + this.globalObject.requestAnimationFrame(this.render.bind(this)) + if (this.globalObject.stopRender || this.renderer?.xr.isPresenting || (this.stopRenderOnBlur && !this.windowFocused)) return + if (this.renderInterval) { + this.delta += time - this.lastTime + this.lastTime = time + if (this.delta > this.renderInterval) { + this.delta %= this.renderInterval + // continue rendering + } else { + return + } + } + // ios bug: viewport dimensions are updated after the resize event + if (this.previousWindowWidth !== window.innerWidth || this.previousWindowHeight !== window.innerHeight) { + this.resizeHandler() + this.previousWindowWidth = window.innerWidth + this.previousWindowHeight = window.innerHeight + } + this.startRender() + viewer.update() + viewer.render() + this.renderedFps++ + this.endRender() + } + + resizeHandler () { + const width = window.innerWidth + const height = window.innerHeight + + viewer.camera.aspect = width / height + viewer.camera.updateProjectionMatrix() + + if (this.renderer) { + this.renderer.setSize(width, height) + } + // canvas updated by renderer + + if (viewer.composer) { + viewer.updateComposerSize() + } + } +} diff --git a/src/index.ts b/src/index.ts index 3cd6bb758..e31923941 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,6 +97,7 @@ import { possiblyHandleStateVariable } from './googledrive' import flyingSquidEvents from './flyingSquidEvents' import { hideNotification, notificationProxy } from './react/NotificationProvider' import { initWebglRenderer } from 'prismarine-viewer/examples/webglRenderer' +import { ViewerBase } from 'prismarine-viewer/viewer/lib/viewerWrapper' window.debug = debug window.THREE = THREE @@ -123,12 +124,6 @@ try { // renderer.localClippingEnabled = true initWithRenderer(renderer.domElement) window.renderer = renderer -let pixelRatio = window.devicePixelRatio || 1 // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable -if (!renderer.capabilities.isWebGL2) pixelRatio = 1 // webgl1 has issues with high pixel ratio (sometimes screen is clipped) -renderer.setPixelRatio(pixelRatio) -renderer.setSize(window.innerWidth, window.innerHeight) -renderer.domElement.id = 'viewer-canvas' -document.body.appendChild(renderer.domElement) const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -173,70 +168,8 @@ watchValue(options, (o) => { }) let postRenderFrameFn = () => { } -let delta = 0 -let lastTime = performance.now() -let previousWindowWidth = window.innerWidth -let previousWindowHeight = window.innerHeight -let max = 0 -let rendered = 0 -let windowFocused = true -window.addEventListener('focus', () => { - windowFocused = true +void initWebglRenderer('1.14.4').then((canvas) => { }) -window.addEventListener('blur', () => { - windowFocused = false -}) -const renderFrame = (time: DOMHighResTimeStamp) => { - if (window.stopLoop) return - for (const fn of beforeRenderFrame) fn() - window.requestAnimationFrame(renderFrame) - if (window.stopRender || renderer.xr.isPresenting || !windowFocused) return - if (renderInterval) { - delta += time - lastTime - lastTime = time - if (delta > renderInterval) { - delta %= renderInterval - // continue rendering - } else { - return - } - } - // ios bug: viewport dimensions are updated after the resize event - if (previousWindowWidth !== window.innerWidth || previousWindowHeight !== window.innerHeight) { - resizeHandler() - previousWindowWidth = window.innerWidth - previousWindowHeight = window.innerHeight - } - statsStart() - viewer.update() - viewer.render() - rendered++ - postRenderFrameFn() - statsEnd() -} -renderFrame(performance.now()) -setInterval(() => { - if (max > 0) { - viewer.world.droppedFpsPercentage = rendered / max - } - max = Math.max(rendered, max) - rendered = 0 -}, 1000) - -const resizeHandler = () => { - const width = window.innerWidth - const height = window.innerHeight - - viewer.camera.aspect = width / height - viewer.camera.updateProjectionMatrix() - renderer.setSize(width, height) - - if (viewer.composer) { - viewer.updateComposerSize() - } -} - -initWebglRenderer('1.14.4') const hud = document.getElementById('hud') const pauseMenu = document.getElementById('pause-screen') From a3290063e86a201a444bcf10b4f5a08f52a4f391 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 12:26:58 +0300 Subject: [PATCH 20/86] restore singleplayer --- prismarine-viewer/examples/playground.ts | 8 - prismarine-viewer/examples/webglRenderer.ts | 26 ++-- .../examples/webglRendererWorker.ts | 144 ++++++++---------- prismarine-viewer/viewer/lib/viewer.ts | 2 - prismarine-viewer/viewer/lib/worldrenderer.ts | 11 +- src/index.ts | 8 +- 6 files changed, 88 insertions(+), 111 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 760802f01..c16f2fa54 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -135,14 +135,6 @@ async function main () { const viewer = new Viewer(nullRenderer, 1) viewer.setVersion(version) globalThis.viewer = viewer - await new Promise(resolve => { - // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) - // viewer.world.material.map!.image.onload = () => { - // console.log(this.material.map!.image) - // resolve() - // } - viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) - }) await initWebglRenderer(version) const simpleControls = () => { diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 217e852a8..322f55696 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -4,22 +4,25 @@ let worker declare const viewer: Viewer -export const makeRender = () => { } - -let cubePositions -let updateCubes -export const updateCubePositions = () => { - updateCubes() -} - -export let cubePositionsRaw = [] as [number, number, number, string | null][] - const sendWorkerMessage = (message: any, transfer?: Transferable[]) => { worker.postMessage(message, transfer) // replacable by onmessage } -export const initWebglRenderer = async (version: string | undefined) => { +export const addBlocksSection = (key, data) => { + sendWorkerMessage({ type: 'addBlocksSection', data, key }) +} + +export const initWebglRenderer = async (version: string, postRender = () => { }) => { + viewer.setVersion(version) + await new Promise(resolve => { + // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) + // viewer.world.material.map!.image.onload = () => { + // console.log(this.material.map!.image) + // resolve() + // } + viewer.world.renderUpdateEmitter.once('blockStatesDownloaded', resolve) + }) const imageBlob = await fetch(`./textures/${version}.png`).then((res) => res.blob()) const canvas = document.createElement('canvas') canvas.width = window.innerWidth * window.devicePixelRatio @@ -64,6 +67,7 @@ export const initWebglRenderer = async (version: string | undefined) => { height: window.innerHeight * window.devicePixelRatio }) } + postRender() if (['rotation', 'position'].some((key) => oldCamera[key] !== viewer.camera[key])) { // TODO fix for (const [key, val] of Object.entries(oldCamera)) { diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index e634d6fa7..753685049 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -6,43 +6,41 @@ import VertShader from './_VertexShader.vert' import FragShader from './_FragmentShader.frag' let blockStates +let newSectionsData = {} +let rendering = true +let cubePositions +let updateCubes -const findTextureInBlockStates = (name): any => { - // assertDefined(viewer) - // const blockStates: BlockStates = viewer.world.customBlockStatesData || viewer.world.downloadedBlockStatesData - // const vars = blockStates[name]?.variants - // if (!vars) return - // let firstVar = Object.values(vars)[0] - // if (Array.isArray(firstVar)) firstVar = firstVar[0] - // if (!firstVar) return - // const elements = firstVar.model?.elements - // if (elements?.length !== 1) return - // return elements[0].faces -} +const camera = new THREE.PerspectiveCamera(75, 1 / 1, 0.1, 1000) -let cubePositionsRaw = [] as [number, number, number, string | null][] +let renderedFrames = 0 +setInterval(() => { + // console.log('FPS:', renderedFrames) + renderedFrames = 0 +}, 1000) -const camera = new THREE.PerspectiveCamera(75, 1 / 1, 0.1, 1000) +const findTextureInBlockStates = (name): any => { + const vars = blockStates[name]?.variants + if (!vars) return + let firstVar = Object.values(vars)[0] as any + if (Array.isArray(firstVar)) firstVar = firstVar[0] + if (!firstVar) return + const elements = firstVar.model?.elements + if (elements?.length !== 1) return + return elements[0].faces +} const updateSize = (width, height) => { camera.aspect = width / height camera.updateProjectionMatrix() } -let renderedFrames = 0 -setInterval(() => { - console.log('FPS:', renderedFrames) - renderedFrames = 0 -}, 1000) - -let rendering = true -let cubePositions -let updateCubes const updateCubePositions = () => { updateCubes() } export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: ImageBitmapSource, blockStatesJson: any) => { + blockStates = blockStatesJson const textureBitmap = await createImageBitmap(imageBlob) const textureWidth = textureBitmap.width const textureHeight = textureBitmap.height @@ -123,29 +121,42 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // [camera.position.x, camera.position.y, camera.position.z + 2, 'dirt'], // [camera.position.x, camera.position.y, camera.position.z - 2, 'dirt'], // ] - // NumberOfCube = cubePositionsRaw.length - // cubePositions = new Float32Array(NumberOfCube * 3) - // cubeTextureIndices = new Float32Array(NumberOfCube); - // for (let i = 0; i < NumberOfCube * 3; i += 3) { - // cubePositions[i] = cubePositionsRaw[i / 3][0] - // cubePositions[i + 1] = cubePositionsRaw[i / 3][1] - // cubePositions[i + 2] = cubePositionsRaw[i / 3][2] - // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); - // const name = cubePositionsRaw[i / 3][3] - // const result = findTextureInBlockStates(name)?.north.texture! ?? findTextureInBlockStates('sponge')?.north.texture! - // const tileSize = 16; - // function uvToTextureIndex (u, v) { - // // Convert pixel coordinates to tile index - // const tileX = Math.floor(u * textureWidth / tileSize); - // const tileY = Math.floor(v * textureHeight / tileSize); - - // // Calculate texture index - // const textureIndex = tileY * (textureWidth / tileSize) + tileX; - - // return textureIndex; - // } - // cubeTextureIndices[i / 3] = uvToTextureIndex(result.u, result.v) - // } + const keys = Object.keys(newSectionsData); + if (keys.length) { + const cubePositionsRaw = keys.flatMap((key: any) => { + const chunk = newSectionsData[key] + return Object.entries(chunk.blocks).map(([pos, blockName]) => { + return [...pos.split(',').map(Number), blockName] as [number, number, number, string] + }) + }) + NumberOfCube = cubePositionsRaw.length + cubePositions = new Float32Array(NumberOfCube * 3) + cubeTextureIndices = new Float32Array(NumberOfCube); + for (let i = 0; i < NumberOfCube * 3; i += 3) { + cubePositions[i] = cubePositionsRaw[i / 3][0] + cubePositions[i + 1] = cubePositionsRaw[i / 3][1] + cubePositions[i + 2] = cubePositionsRaw[i / 3][2] + cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); + const name = cubePositionsRaw[i / 3][3] + const result = findTextureInBlockStates(name)?.north?.texture! ?? findTextureInBlockStates('sponge')?.north.texture! + const tileSize = 16; + function uvToTextureIndex (u, v) { + // Convert UV coordinates to pixel coordinates + let x = u * textureWidth; + let y = v * textureHeight; + + // Convert pixel coordinates to tile index + const tileX = Math.floor(x / tileSize); + const tileY = Math.floor(y / tileSize); + + // Calculate texture index + const textureIndex = tileY * (textureWidth / tileSize) + tileX; + + return textureIndex; + } + cubeTextureIndices[i / 3] = uvToTextureIndex(result.u, result.v) - 1 + } + } let instanceVBO = gl.createBuffer(); let instanceTextureID = gl.createBuffer(); @@ -257,6 +268,8 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im updateSize(gl.canvas.width, gl.canvas.height) const renderLoop = (performance) => { + requestAnimationFrame(renderLoop) + if (!rendering) return // gl.canvas.width = window.innerWidth * window.devicePixelRatio // gl.canvas.height = window.innerHeight * window.devicePixelRatio if (newWidth || newHeight) { @@ -298,41 +311,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, NumberOfCube); //gl.bindVertexArray(null) - //let i = 0 - // const cubePositions = Object.values(viewer.world.newChunks).map((chunk: any) => { - // return Object.entries(chunk.blocks).map(([pos, block]) => { - // return [...pos.split(',').map(Number), block] as [number, number, number, string] - // }) - // }).flat() - - - // cubePositions.forEach(([x, y, z, name]) => { - // if (result || true) { - // const model = m4.identity() - - // //m4.rotateX(model, performance / 1000*i/800 + Math.random() / 100, model); - // //m4.rotateY(model, performance / 2500*i/800 + Math.random() / 100, model) - // //m4.rotateZ(model, Math.random() / 1010, model) - // m4.translate(model, [x, y, z], model); - // gl.uniformMatrix4fv(ModelUniform, false, model); - // const u = i / 64; - // const v = i % 64; - // // const u = result.u + result.su - // // const v = result.v - // gl.uniform2fv(uvUniform, [u, v]) - - // i++ - // i %= 800; - - // gl.drawArrays(gl.TRIANGLES, 0, 36); - // } - // }) - - - ///model.translate([0, 0, 0], model) - renderedFrames++ - requestAnimationFrame(renderLoop) } requestAnimationFrame(renderLoop) @@ -394,8 +373,9 @@ onmessage = function (e) { newWidth = e.data.newWidth newHeight = e.data.newHeight } - if (e.data.cubePositionsRaw) { - cubePositionsRaw = e.data.cubePositionsRaw + if (e.data.type === 'addBlocksSection') { + newSectionsData[e.data.key] = e.data.data + updateCubes?.() } if (e.data.type === 'camera') { camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index e7fbd4b9b..82cbc64d6 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,7 +7,6 @@ import { Primitives } from './primitives' import { getVersion } from './version' import EventEmitter from 'events' import { EffectComposer, RenderPass, ShaderPass, FXAAShader } from 'three-stdlib' -import { makeRender } from '../../examples/webglRenderer' export class Viewer { scene: THREE.Scene @@ -194,7 +193,6 @@ export class Viewer { } render () { - makeRender() // if (this.composer) { // this.renderPass.camera = this.camera // this.composer.render() diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts index 553c33dc8..10da0eef3 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ b/prismarine-viewer/viewer/lib/worldrenderer.ts @@ -10,7 +10,7 @@ import { toMajor } from './version.js' import PrismarineChatLoader from 'prismarine-chat' import { renderSign } from '../sign-renderer/' import { chunkPos, sectionPos } from './simpleUtils' -import { cubePositionsRaw, updateCubePositions } from '../../examples/webglRenderer' +import { addBlocksSection } from '../../examples/webglRenderer' function mod (x, n) { return ((x % n) + n) % n @@ -89,11 +89,12 @@ export class WorldRenderer { const chunkCoords = data.key.split(',') if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return + addBlocksSection(data.key, data.geometry) this.newChunks[data.key] = data.geometry - cubePositionsRaw.push(...Object.entries(data.geometry.blocks).map(([pos, block]) => { - return [...pos.split(',').map(Number), block] as [number, number, number, string] - })) - updateCubePositions() + // cubePositionsBySections[data.key].push(...Object.entries(data.geometry.blocks).map(([pos, block]) => { + // return [...pos.split(',').map(Number), block] as [number, number, number, string] + // })) + // updateCubePositions() // if (!this.initialChunksLoad && this.enableChunksLoadDelay) { // const newPromise = new Promise(resolve => { diff --git a/src/index.ts b/src/index.ts index e31923941..a4cad8886 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,7 +97,7 @@ import { possiblyHandleStateVariable } from './googledrive' import flyingSquidEvents from './flyingSquidEvents' import { hideNotification, notificationProxy } from './react/NotificationProvider' import { initWebglRenderer } from 'prismarine-viewer/examples/webglRenderer' -import { ViewerBase } from 'prismarine-viewer/viewer/lib/viewerWrapper' +// import { ViewerBase } from 'prismarine-viewer/viewer/lib/viewerWrapper' window.debug = debug window.THREE = THREE @@ -168,8 +168,6 @@ watchValue(options, (o) => { }) let postRenderFrameFn = () => { } -void initWebglRenderer('1.14.4').then((canvas) => { -}) const hud = document.getElementById('hud') const pauseMenu = document.getElementById('pause-screen') @@ -387,6 +385,10 @@ async function connect (connectOptions: { if (downloadVersion) { await downloadMcData(downloadVersion) } + await initWebglRenderer(downloadVersion, () => { + postRenderFrameFn() + // viewer.update() + }) if (singleplayer) { // SINGLEPLAYER EXPLAINER: From ce194599d74f749d0a76acdfd68f978e96998a64 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 12:27:03 +0300 Subject: [PATCH 21/86] up world --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc673ee31..fb79e917f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5734,7 +5734,7 @@ packages: prismarine-nbt: 2.5.0 prismarine-provider-anvil: github.com/zardoy/prismarine-provider-anvil/0ddcd9d48574113308e1fbebef60816aced0846f(minecraft-data@3.62.0) prismarine-windows: 2.9.0 - prismarine-world: github.com/zardoy/prismarine-world/c358222204d21fe7d45379fbfcefb047f926c786 + prismarine-world: github.com/zardoy/prismarine-world/6ae6f009d38460de284f8c226c665f04cbad9465 random-seed: 0.3.0 range: 0.0.3 readline: 1.3.0 @@ -12505,7 +12505,7 @@ packages: minecraft-data: 3.62.0 prismarine-block: github.com/zardoy/prismarine-block/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 prismarine-nbt: 2.2.1 - prismarine-world: github.com/zardoy/prismarine-world/c358222204d21fe7d45379fbfcefb047f926c786 + prismarine-world: github.com/zardoy/prismarine-world/6ae6f009d38460de284f8c226c665f04cbad9465 vec3: 0.1.8 dev: false @@ -15955,7 +15955,7 @@ packages: prismarine-recipe: 1.3.1(prismarine-registry@1.7.0) prismarine-registry: 1.7.0 prismarine-windows: 2.9.0 - prismarine-world: github.com/zardoy/prismarine-world/c358222204d21fe7d45379fbfcefb047f926c786 + prismarine-world: github.com/zardoy/prismarine-world/6ae6f009d38460de284f8c226c665f04cbad9465 protodef: 1.15.0 typed-emitter: 1.4.0 vec3: 0.1.8 @@ -16063,8 +16063,8 @@ packages: - minecraft-data dev: false - github.com/zardoy/prismarine-world/c358222204d21fe7d45379fbfcefb047f926c786: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-world/tar.gz/c358222204d21fe7d45379fbfcefb047f926c786} + github.com/zardoy/prismarine-world/6ae6f009d38460de284f8c226c665f04cbad9465: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465} name: prismarine-world version: 3.6.2 engines: {node: '>=8.0.0'} From 00b9683e782608ee49b7d6bc1090af31365bb0f5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 12:35:33 +0300 Subject: [PATCH 22/86] fix worker output! --- buildWorkers.mjs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/buildWorkers.mjs b/buildWorkers.mjs index 9807c4593..b8d16d115 100644 --- a/buildWorkers.mjs +++ b/buildWorkers.mjs @@ -1,5 +1,6 @@ // main worker file intended for computing world geometry is built using prismarine-viewer/buildWorker.mjs import { build, context } from 'esbuild' +import fs from 'fs' const watch = process.argv.includes('-w') @@ -8,10 +9,21 @@ const result = await (watch ? context : build)({ platform: 'browser', entryPoints: ['prismarine-viewer/examples/webglRendererWorker.ts'], outfile: 'prismarine-viewer/public/webglRendererWorker.js', - sourcemap: 'inline', - // minify: true, + sourcemap: watch ? 'inline' : 'external', + minify: !watch, logLevel: 'info', - plugins: [], + plugins: [ + { + name: 'writeOutput', + setup (build) { + build.onEnd(({ outputFiles }) => { + for (const file of ['prismarine-viewer/public/webglRendererWorker.js', 'dist/webglRendererWorker.js']) { + fs.writeFileSync(file, outputFiles[0].text, 'utf8') + } + }) + } + } + ], loader: { '.vert': 'text', '.frag': 'text' @@ -20,6 +32,7 @@ const result = await (watch ? context : build)({ 'browser', 'module', 'main' ], keepNames: true, + write: false, }) if (watch) { From d0a6941031887aee837a1d35c2312844180e684e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 12:40:39 +0300 Subject: [PATCH 23/86] build workers --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98f6727b5..3fe21a9d4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "node scripts/build.js copyFilesDev && node scripts/prepareData.mjs && node esbuild.mjs --watch", "start-watch-script": "nodemon -w esbuild.mjs --watch", - "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod", + "build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod && node buildWorkers.mjs", "check-build": "tsc && pnpm test-unit && pnpm build", "test:cypress": "cypress run", "test:cypress:perf": "cypress run --spec cypress/e2e/perf.spec.ts --browser edge", From f3295593bf1b024bcc184f6ca39786d25f301829 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 13:00:24 +0300 Subject: [PATCH 24/86] build worker correctly --- buildWorkers.mjs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/buildWorkers.mjs b/buildWorkers.mjs index b8d16d115..b4dec8f51 100644 --- a/buildWorkers.mjs +++ b/buildWorkers.mjs @@ -11,14 +11,21 @@ const result = await (watch ? context : build)({ outfile: 'prismarine-viewer/public/webglRendererWorker.js', sourcemap: watch ? 'inline' : 'external', minify: !watch, + treeShaking: true, logLevel: 'info', + alias: { + 'three': './node_modules/three/src/Three.js' + }, plugins: [ { name: 'writeOutput', setup (build) { build.onEnd(({ outputFiles }) => { - for (const file of ['prismarine-viewer/public/webglRendererWorker.js', 'dist/webglRendererWorker.js']) { - fs.writeFileSync(file, outputFiles[0].text, 'utf8') + for (const file of outputFiles) { + for (const dir of ['prismarine-viewer/public', 'dist']) { + const baseName = file.path.split('/').pop() + fs.writeFileSync(`${dir}/${baseName}`, file.contents) + } } }) } From a19c9ad784588e4132474d2512f803301ebbab1b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 14:37:37 +0300 Subject: [PATCH 25/86] fix cam --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index a4cad8886..1ee933915 100644 --- a/src/index.ts +++ b/src/index.ts @@ -387,7 +387,7 @@ async function connect (connectOptions: { } await initWebglRenderer(downloadVersion, () => { postRenderFrameFn() - // viewer.update() + viewer.update() }) if (singleplayer) { From 7d5f1c504f420631df79b15d084b4d2bcdfa9392 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 27 Mar 2024 11:16:07 +0300 Subject: [PATCH 26/86] worker part 2 --- buildWorkers.mjs | 19 ++++++++++++++++--- src/workerWorkaround.ts | 2 ++ src/worldSaveWorker.ts | 2 ++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/workerWorkaround.ts create mode 100644 src/worldSaveWorker.ts diff --git a/buildWorkers.mjs b/buildWorkers.mjs index b4dec8f51..c6bdcb220 100644 --- a/buildWorkers.mjs +++ b/buildWorkers.mjs @@ -7,15 +7,28 @@ const watch = process.argv.includes('-w') const result = await (watch ? context : build)({ bundle: true, platform: 'browser', - entryPoints: ['prismarine-viewer/examples/webglRendererWorker.ts'], - outfile: 'prismarine-viewer/public/webglRendererWorker.js', + entryPoints: ['prismarine-viewer/examples/webglRendererWorker.ts', 'src/worldSaveWorker.ts'], + outdir: 'prismarine-viewer/public/', sourcemap: watch ? 'inline' : 'external', minify: !watch, treeShaking: true, logLevel: 'info', alias: { - 'three': './node_modules/three/src/Three.js' + 'three': './node_modules/three/src/Three.js', + events: 'events', // make explicit + buffer: 'buffer', + 'fs': 'browserfs/dist/shims/fs.js', + http: 'http-browserify', + perf_hooks: './src/perf_hooks_replacement.js', + crypto: './src/crypto.js', + stream: 'stream-browserify', + net: 'net-browserify', + assert: 'assert', + dns: './src/dns.js' }, + inject: [ + './src/shims.js' + ], plugins: [ { name: 'writeOutput', diff --git a/src/workerWorkaround.ts b/src/workerWorkaround.ts new file mode 100644 index 000000000..00419b8c3 --- /dev/null +++ b/src/workerWorkaround.ts @@ -0,0 +1,2 @@ +global = globalThis +globalThis.window = globalThis diff --git a/src/worldSaveWorker.ts b/src/worldSaveWorker.ts new file mode 100644 index 000000000..b49c02003 --- /dev/null +++ b/src/worldSaveWorker.ts @@ -0,0 +1,2 @@ +import './workerWorkaround' +import './browserfs' From 506706e88b74d9dc509edd95121b8f21d51b5f55 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 28 Mar 2024 03:43:42 +0300 Subject: [PATCH 27/86] fixed camera, coords, cam, textures, controls --- buildWorkers.mjs | 2 +- experiments/texture-render.html | 21 +++--- .../examples/_FragmentShader.frag | 12 ++-- prismarine-viewer/examples/_VertexShader.vert | 3 +- prismarine-viewer/examples/playground.ts | 65 ++++++++++++------- prismarine-viewer/examples/webglRenderer.ts | 63 +++++++++++++++++- .../examples/webglRendererWorker.ts | 59 ++++++++--------- src/topRightStats.ts | 2 +- 8 files changed, 156 insertions(+), 71 deletions(-) diff --git a/buildWorkers.mjs b/buildWorkers.mjs index c6bdcb220..a7008de58 100644 --- a/buildWorkers.mjs +++ b/buildWorkers.mjs @@ -7,7 +7,7 @@ const watch = process.argv.includes('-w') const result = await (watch ? context : build)({ bundle: true, platform: 'browser', - entryPoints: ['prismarine-viewer/examples/webglRendererWorker.ts', 'src/worldSaveWorker.ts'], + entryPoints: ['prismarine-viewer/examples/webglRendererWorker.ts'], outdir: 'prismarine-viewer/public/', sourcemap: watch ? 'inline' : 'external', minify: !watch, diff --git a/experiments/texture-render.html b/experiments/texture-render.html index be406102a..585f10e5a 100644 --- a/experiments/texture-render.html +++ b/experiments/texture-render.html @@ -10,8 +10,8 @@ diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index bcc02a06d..8f9d53ce4 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -419,127 +419,126 @@ export function getSectionGeometry (sx, sy, sz, world: World) { block.variant = getModelVariants(block) } - for (const variant of block.variant) { - if (!variant || !variant.model) continue - - if (true/* block.name !== 'water' && block.name !== 'lava' *//* && block.isCube */) { - let globalMatrix = null as any - let globalShift = null as any + if (/* block.name !== 'water' && block.name !== 'lava' *//* && block.isCube */block.name !== 'air') { + let globalMatrix = null as any + let globalShift = null as any + if (block.variant?.model) { for (const axis of ['x', 'y', 'z']) { - if (axis in variant) { - if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) - else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) + if (axis in block.variant) { + if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -block.variant[axis]) + else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -block.variant[axis])) + } + } + } + + if (globalMatrix) { + globalShift = [8, 8, 8] + globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) + } + + // full cube rendering + for (const face in elemFaces) { + const cullIfIdentical = block.name.indexOf('glass') >= 0 + + // const eFace = element.faces[face] + const { corners, mask1, mask2 } = elemFaces[face] + const dir = matmul3(globalMatrix, elemFaces[face].dir) + + if (/* eFace.cullface */true) { + const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) + if (neighbor) { + if (cullIfIdentical && neighbor.type === block.type) continue + if (!neighbor.transparent && neighbor.isCube) continue + } else { + continue } } - if (globalMatrix) { - globalShift = [8, 8, 8] - globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) + const pos = block.position + + const findTextureInBlockStates = (name): any => { + const vars = blockStates[name]?.variants + if (!vars) return blockStates[name]?.multipart?.[0]?.apply?.[0]?.model?.elements?.[0]?.faces?.south?.texture + let firstVar = Object.values(vars)[0] as any + if (Array.isArray(firstVar)) firstVar = firstVar[0] + if (!firstVar) return + const [element] = firstVar.model?.elements + if (!element) return firstVar.model?.textures?.particle + if (!element/* || !(element?.from.every(a => a === 0) && element?.to.every(a => a === 16)) */) return + return element.faces } - for (const element of variant.model.elements) { - for (const face in element.faces) { - const cullIfIdentical = block.name.indexOf('glass') >= 0 - - const eFace = element.faces[face] - const { corners, mask1, mask2 } = elemFaces[face] - const dir = matmul3(globalMatrix, elemFaces[face].dir) - - if (eFace.cullface) { - const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) - if (neighbor) { - if (cullIfIdentical && neighbor.type === block.type) continue - if (!neighbor.transparent && neighbor.isCube) continue - } else { - continue - } - } - - const pos = block.position - - const findTextureInBlockStates = (name): any => { - const vars = blockStates[name]?.variants - if (!vars) return blockStates[name]?.multipart?.[0]?.apply?.[0]?.model?.elements?.[0]?.faces?.south?.texture - let firstVar = Object.values(vars)[0] as any - if (Array.isArray(firstVar)) firstVar = firstVar[0] - if (!firstVar) return - const [element] = firstVar.model?.elements - if (!element) return firstVar.model?.textures?.particle - if (!element/* || !(element?.from.every(a => a === 0) && element?.to.every(a => a === 16)) */) return - return element.faces - } - - let animatedFrames = undefined - const getResult = (side: string): number => { - const facesOrTexture = findTextureInBlockStates(block.name); - if (!facesOrTexture) return - const result = 'u' in facesOrTexture ? facesOrTexture : facesOrTexture?.[side]?.texture - if (!result) return 0 // todo - if (result.animatedFrames) { - animatedFrames = result.animatedFrames - } - return uvToTextureIndex(result.u, result.v) - (result.su < 0 ? 1 : 0) - (result.sv < 0 ? 1 : 0) - } - function uvToTextureIndex (u, v) { - const textureWidth = textureSize - const textureHeight = textureSize - const tileSize = 16; - // Convert UV coordinates to pixel coordinates - let x = u * textureWidth; - let y = v * textureHeight; - - // Convert pixel coordinates to tile index - const tileX = Math.floor(x / tileSize); - const tileY = Math.floor(y / tileSize); - - // Calculate texture index - const textureIndex = tileY * (textureWidth / tileSize) + tileX; - - return textureIndex; - } - // back, front, left, right, top, bottom - const textures = [ - getResult('north'), - getResult('south'), - getResult('west'), - getResult('east'), - getResult('up'), - getResult('down') - ] - if (textures.every(t => t === 0)) continue - attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { - textureIndex: textures, - animatedFrames - } satisfies BlockType + let animatedFrames = undefined + const getResult = (side: string): number => { + const facesOrTexture = findTextureInBlockStates(block.name); + if (!facesOrTexture) return + const result = 'u' in facesOrTexture ? facesOrTexture : facesOrTexture?.[side]?.texture + if (!result) return 0 // todo + if (result.animatedFrames) { + animatedFrames = result.animatedFrames } + return uvToTextureIndex(result.u, result.v) - (result.su < 0 ? 1 : 0) - (result.sv < 0 ? 1 : 0) + } + function uvToTextureIndex (u, v) { + const textureWidth = textureSize + const textureHeight = textureSize + const tileSize = 16; + // Convert UV coordinates to pixel coordinates + let x = u * textureWidth; + let y = v * textureHeight; + + // Convert pixel coordinates to tile index + const tileX = Math.floor(x / tileSize); + const tileY = Math.floor(y / tileSize); + + // Calculate texture index + const textureIndex = tileY * (textureWidth / tileSize) + tileX; + + return textureIndex; } + // back, front, left, right, top, bottom + const textures = [ + getResult('north'), + getResult('south'), + getResult('west'), + getResult('east'), + getResult('up'), + getResult('down') + ] + if (textures.every(t => t === 0)) continue + attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { + textureIndex: textures, + animatedFrames + } satisfies BlockType } - // if (block.name === 'water') { - // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) - // } else if (block.name === 'lava') { - // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) - // } else { - // let globalMatrix = null as any - // let globalShift = null as any - - // for (const axis of ['x', 'y', 'z']) { - // if (axis in variant) { - // if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) - // else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) - // } - // } - - // if (globalMatrix) { - // globalShift = [8, 8, 8] - // globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) - // } - - // for (const element of variant.model.elements) { - // renderElement(world, cursor, element, variant.model.ao, attr, globalMatrix, globalShift, block, biome) - // } - // } + } + // if (block.name === 'water') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) + // } else if (block.name === 'lava') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) + // } else { + // let globalMatrix = null as any + // let globalShift = null as any + + // for (const axis of ['x', 'y', 'z']) { + // if (axis in variant) { + // if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) + // else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) + // } + // } + + // if (globalMatrix) { + // globalShift = [8, 8, 8] + // globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) + // } + + // for (const element of variant.model.elements) { + // renderElement(world, cursor, element, variant.model.ao, attr, globalMatrix, globalShift, block, biome) + // } + // } + } } } diff --git a/prismarine-viewer/viewer/prepare/atlas.ts b/prismarine-viewer/viewer/prepare/atlas.ts index 694cefa85..546f83bd7 100644 --- a/prismarine-viewer/viewer/prepare/atlas.ts +++ b/prismarine-viewer/viewer/prepare/atlas.ts @@ -62,6 +62,12 @@ export const makeTextureAtlas = (input: string[], getInputData: (name) => { cont img.src = inputData.contents const renderWidth = tileSize * (inputData.tileWidthMult ?? 1) let animatedFrames = 0 + const addDebugText = (x, y) => { + // return // disable debug text + g.fillStyle = 'black' + g.font = '8px Arial' + g.fillText(i, x, y) + } if (img.height > tileSize) { const frames = img.height / tileSize; animatedFrames = frames @@ -71,9 +77,11 @@ export const makeTextureAtlas = (input: string[], getInputData: (name) => { cont const x = ((pos + i) % texSize) * tileSize const y = Math.floor((pos + i) / texSize) * tileSize g.drawImage(img, 0, i * tileSize, renderWidth, tileSize, x, y, renderWidth, tileSize) + addDebugText(x, y) } } else { g.drawImage(img, 0, 0, renderWidth, tileSize, x, y, renderWidth, tileSize) + addDebugText(x, y) } const cleanName = keyValue.split('.').slice(0, -1).join('.') || keyValue diff --git a/prismarine-viewer/viewer/prepare/generateTextures.ts b/prismarine-viewer/viewer/prepare/generateTextures.ts index b9a5e3e5f..93f7a52ca 100644 --- a/prismarine-viewer/viewer/prepare/generateTextures.ts +++ b/prismarine-viewer/viewer/prepare/generateTextures.ts @@ -22,9 +22,10 @@ const warnings = new Set() Promise.resolve().then(async () => { generateItemsAtlases() console.time('generateTextures') - for (const version of mcAssets.versions as typeof mcAssets['versions']) { + const versions = process.argv.includes('-l') ? mcAssets.versions.at(-1) : mcAssets.versions + for (const version of versions as typeof mcAssets['versions']) { // for debugging (e.g. when above is overridden) - if (!mcAssets.versions.includes(version)) { + if (!versions.includes(version)) { throw new Error(`Version ${version} is not supported by minecraft-assets`) } const assets = mcAssets(version) @@ -44,7 +45,7 @@ Promise.resolve().then(async () => { fs.copySync(assets.directory, path.resolve(texturesPath, version), { overwrite: true }) } - fs.writeFileSync(path.join(publicPath, 'supportedVersions.json'), '[' + mcAssets.versions.map(v => `"${v}"`).toString() + ']') + fs.writeFileSync(path.join(publicPath, 'supportedVersions.json'), '[' + versions.map(v => `"${v}"`).toString() + ']') warnings.forEach(x => console.warn(x)) console.timeEnd('generateTextures') }) From 97a9e92119237bd05822daea71fb9d9d3a0c0340 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 6 Apr 2024 02:55:44 +0300 Subject: [PATCH 44/86] impl basic debug animation --- prismarine-viewer/examples/playground.ts | 10 ++++++---- prismarine-viewer/examples/webglRenderer.ts | 5 +++-- .../examples/webglRendererWorker.ts | 20 +++++++++++++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 2ffa62255..5ceef5190 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -71,7 +71,7 @@ async function main () { let continuousRender = false // const { version } = params - const version = '1.16.4' + const version = '1.20.2' // temporary solution until web worker is here, cache data for faster reloads const globalMcData = window['mcData'] if (!globalMcData['version']) { @@ -101,7 +101,7 @@ async function main () { gui.add(params, 'skip') gui.add(params, 'playSound') gui.add(params, 'blockIsomorphicRenderBundle') - const animationController = gui.add(params, 'animationTick', 0, 20, 1).listen() + const animationController = gui.add(params, 'animationTick', -1, 20, 1).listen() gui.open(false) let metadataFolder = gui.addFolder('metadata') // let entityRotationFolder = gui.addFolder('entity metadata') @@ -365,7 +365,7 @@ async function main () { viewer.setBlockStateId(targetPos.offset(0, -1, 0), params.supportBlock ? 1 : 0) }, animationTick () { - setAnimationTick(params.animationTick) + setAnimationTick(params.animationTick, viewer.world.hasWithFrames - 1) } } @@ -430,7 +430,9 @@ async function main () { window.requestAnimationFrame(animate2) } viewer.world.renderUpdateEmitter.addListener('update', () => { - // animationController.max(viewer.world.hasWithFrames ?? 0) + const frames = viewer.world.hasWithFrames ? viewer.world.hasWithFrames - 1 : 0; + animationController.max(frames) + onUpdate.animationTick() }) animate2() diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 3f4f93cae..adb5b9759 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -143,10 +143,11 @@ export const initWebglRenderer = async (version: string, postRender = () => { }, requestAnimationFrame(mainLoop) } -export const setAnimationTick = (tick: number) => { +export const setAnimationTick = (tick: number, frames?: number) => { sendWorkerMessage({ type: 'animationTick', - tick + tick, + frames }) } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index dd7f1a660..165e94c64 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -290,7 +290,6 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindVertexArray(VAO) updateSize(gl.canvas.width, gl.canvas.height) - // setInterval(() => tick = (tick + 1) % 20, 1) const renderLoop = (performance) => { requestAnimationFrame(renderLoop) if (!rendering) return @@ -367,6 +366,7 @@ const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmen let started = false let newWidth: number | undefined let newHeight: number | undefined +let autoTickUpdate = undefined as number | undefined onmessage = function (e) { if (!started) { started = true @@ -431,6 +431,22 @@ onmessage = function (e) { camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) } if (e.data.type === 'animationTick') { - animationTick = e.data.tick % 20 // todo update automatically in worker + if (e.data.frames <= 0) { + autoTickUpdate = undefined + animationTick = 0 + return + } + if (e.data.tick === -1) { + autoTickUpdate = e.data.frames + } else { + autoTickUpdate = undefined + animationTick = e.data.tick % 20 // todo update automatically in worker + } } } + +setInterval(() => { + if (autoTickUpdate) { + animationTick = (animationTick + 1) % autoTickUpdate; + } +}, 1000 / 20) From 8b6e1d210bd6b6975e2920d827c6a8b2b7ed0f84 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 6 Apr 2024 19:00:13 +0300 Subject: [PATCH 45/86] make worldrenderer extendable and customizable with your own render implementations! --- prismarine-viewer/viewer/lib/viewer.ts | 9 +- prismarine-viewer/viewer/lib/worldrenderer.ts | 413 ------------------ .../viewer/lib/worldrendererCommon.ts | 203 +++++++++ .../viewer/lib/worldrendererThree.ts | 214 +++++++++ .../viewer/lib/worldrendererWebgl.ts | 59 +++ 5 files changed, 481 insertions(+), 417 deletions(-) delete mode 100644 prismarine-viewer/viewer/lib/worldrenderer.ts create mode 100644 prismarine-viewer/viewer/lib/worldrendererCommon.ts create mode 100644 prismarine-viewer/viewer/lib/worldrendererThree.ts create mode 100644 prismarine-viewer/viewer/lib/worldrendererWebgl.ts diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 854f86580..5a457c297 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -1,20 +1,21 @@ import * as THREE from 'three' import * as tweenJs from '@tweenjs/tween.js' import { Vec3 } from 'vec3' -import { WorldRenderer } from './worldrenderer' +import { WorldRendererWebgl } from './worldrendererWebgl' import { Entities } from './entities' import { Primitives } from './primitives' import { getVersion } from './version' import EventEmitter from 'events' import { EffectComposer, RenderPass, ShaderPass, FXAAShader } from 'three-stdlib' import { sendCameraToWorker } from '../../examples/webglRenderer' +import { WorldRendererThree } from './worldrendererThree' export class Viewer { scene: THREE.Scene ambientLight: THREE.AmbientLight directionalLight: THREE.DirectionalLight camera: THREE.PerspectiveCamera - world: WorldRenderer + world: WorldRendererWebgl | WorldRendererThree entities: Entities primitives: Primitives domElement: HTMLCanvasElement @@ -40,7 +41,7 @@ export class Viewer { if (this.enableFXAA) { this.enableFxaaScene() } - this.world = new WorldRenderer(this.scene, numWorkers) + this.world = new WorldRendererWebgl(numWorkers) this.entities = new Entities(this.scene) this.primitives = new Primitives(this.scene, this.camera) @@ -162,7 +163,7 @@ export class Viewer { }) // todo remove and use other architecture instead so data flow is clear emitter.on('blockEntities', (blockEntities) => { - this.world.blockEntities = blockEntities + (this.world as WorldRendererThree).blockEntities = blockEntities }) emitter.on('unloadChunk', ({ x, z }) => { diff --git a/prismarine-viewer/viewer/lib/worldrenderer.ts b/prismarine-viewer/viewer/lib/worldrenderer.ts deleted file mode 100644 index 0bad8fed4..000000000 --- a/prismarine-viewer/viewer/lib/worldrenderer.ts +++ /dev/null @@ -1,413 +0,0 @@ -import * as THREE from 'three' -import { Vec3 } from 'vec3' -import { loadTexture, loadJSON } from './utils' -import { EventEmitter } from 'events' -import mcDataRaw from 'minecraft-data/data.js' // handled correctly in esbuild plugin -import nbt from 'prismarine-nbt' -import { dynamicMcDataFiles } from '../../buildWorkerConfig.mjs' -import { dispose3 } from './dispose' -import { toMajor } from './version.js' -import PrismarineChatLoader from 'prismarine-chat' -import { renderSign } from '../sign-renderer/' -import { chunkPos, sectionPos } from './simpleUtils' -import { addBlocksSection, removeBlocksSection } from '../../examples/webglRenderer' - -function mod (x, n) { - return ((x % n) + n) % n -} - -export type WorldHolder = { - add (opt: { - geometry: { - positions: Float32Array, - normals: Float32Array, - colors: Float32Array, - uvs: Float32Array, - indices: Uint32Array, - sx: number, - sy: number, - sz: number, - signs: Record - } - }) - remove (opt: { key: string }) -} - -export class WorldRenderer { - worldConfig = { minY: 0, worldHeight: 256 } - // todo @sa2urami set alphaTest back to 0.1 and instead properly sort transparent and solid objects (needs to be done in worker too) - material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.5 }) - - blockEntities = {} - hasWithFrames = undefined as number | undefined - sectionObjects: Record = {} - showChunkBorders = false - active = false - version = undefined as string | undefined - chunkTextures = new Map() - loadedChunks = {} - sectionsOutstanding = new Set() - renderUpdateEmitter = new EventEmitter() - customBlockStatesData = undefined as any - customTexturesDataUrl = undefined as string | undefined - downloadedBlockStatesData = undefined as any - downloadedTextureImage = undefined as any - workers: any[] = [] - viewerPosition?: Vec3 - lastCamUpdate = 0 - droppedFpsPercentage = 0 - initialChunksLoad = true - enableChunksLoadDelay = false - newChunks = {} - - texturesVersion?: string - - promisesQueue = [] as Promise[] - - constructor (public holder: unknown, numWorkers = 4) { - // init workers - for (let i = 0; i < numWorkers; i++) { - // Node environment needs an absolute path, but browser needs the url of the file - let src = __dirname - if (typeof window === 'undefined') src += '/worker.js' - else src = 'worker.js' - - const worker: any = new Worker(src) - worker.onmessage = async ({ data }) => { - if (!this.active) return - await new Promise(resolve => { - setTimeout(resolve, 0) - }) - if (data.type === 'geometry') { - // let object: THREE.Object3D = this.sectionObjects[data.key] - // if (object) { - // this.scene.remove(object) - // dispose3(object) - // delete this.sectionObjects[data.key] - // } - // if - - const chunkCoords = data.key.split(',') - if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return - - addBlocksSection(data.key, data.geometry) - const blocks = Object.values(data.geometry.blocks) as any[] - const animatedFrames = blocks.find((x: any) => { - return x.animatedFrames - }); - this.hasWithFrames = animatedFrames?.animatedFrames - this.newChunks[data.key] = data.geometry - // cubePositionsBySections[data.key].push(...Object.entries(data.geometry.blocks).map(([pos, block]) => { - // return [...pos.split(',').map(Number), block] as [number, number, number, string] - // })) - // updateCubePositions() - - // if (!this.initialChunksLoad && this.enableChunksLoadDelay) { - // const newPromise = new Promise(resolve => { - // if (this.droppedFpsPercentage > 0.5) { - // setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage) - // } else { - // setTimeout(resolve) - // } - // }) - // this.promisesQueue.push(newPromise) - // for (const promise of this.promisesQueue) { - // await promise - // } - // } - - return - - // const geometry = new THREE.BufferGeometry() - // geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) - // geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3)) - // geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3)) - // geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2)) - // geometry.setIndex(data.geometry.indices) - - // const mesh = new THREE.Mesh(geometry, this.material) - // mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) - // mesh.name = 'mesh' - // object = new THREE.Group() - // object.add(mesh) - // const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) - // boxHelper.name = 'helper' - // object.add(boxHelper) - // object.name = 'chunk' - // if (!this.showChunkBorders) { - // boxHelper.visible = false - // } - // // should not compute it once - // if (Object.keys(data.geometry.signs).length) { - // for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { - // const [x, y, z] = posKey.split(',') - // const signBlockEntity = this.blockEntities[posKey] - // if (!signBlockEntity) continue - // const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity)); - // if (!sign) continue - // object.add(sign) - // } - // } - // this.sectionObjects[data.key] = object - // this.updatePosDataChunk(data.key) - // this.scene.add(object) - } else if (data.type === 'sectionFinished') { - this.sectionsOutstanding.delete(data.key) - this.renderUpdateEmitter.emit('update') - } - } - if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) }) - this.workers.push(worker) - } - } - - /** - * Optionally update data that are depedendent on the viewer position - */ - updatePosDataChunk (key: string) { - if (!this.viewerPosition) return - const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16)) - const [xPlayer, yPlayer, zPlayer] = this.viewerPosition.toArray().map(x => Math.floor(x / 16)) - // sum of distances: x + y + z - const chunkDistance = Math.abs(x - xPlayer) + Math.abs(y - yPlayer) + Math.abs(z - zPlayer) - const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')! - section.renderOrder = 500 - chunkDistance - } - - updateViewerPosition (pos: Vec3) { - this.viewerPosition = pos - for (const key of Object.keys(this.sectionObjects)) { - this.updatePosDataChunk(key) - } - } - - signsCache = new Map() - - getSignTexture (position: Vec3, blockEntity, backSide = false) { - const chunk = chunkPos(position) - let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`) - if (!textures) { - textures = {} - this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures) - } - const texturekey = `${position.x},${position.y},${position.z}`; - // todo investigate bug and remove this so don't need to clean in section dirty - if (textures[texturekey]) return textures[texturekey] - - const PrismarineChat = PrismarineChatLoader(this.version!) - const canvas = renderSign(blockEntity, PrismarineChat) - if (!canvas) return - const tex = new THREE.Texture(canvas) - tex.magFilter = THREE.NearestFilter - tex.minFilter = THREE.NearestFilter - tex.needsUpdate = true - textures[texturekey] = tex - return tex - } - - renderSign (position: Vec3, rotation: number, isWall: boolean, blockEntity) { - const tex = this.getSignTexture(position, blockEntity) - - if (!tex) return - - // todo implement - // const key = JSON.stringify({ position, rotation, isWall }) - // if (this.signsCache.has(key)) { - // console.log('cached', key) - // } else { - // this.signsCache.set(key, tex) - // } - - const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true, })) - mesh.renderOrder = 999 - - // todo @sa2urami shouldnt all this be done in worker? - mesh.scale.set(1, 7 / 16, 1) - if (isWall) { - mesh.position.set(0, 0, -(8 - 1.5) / 16 + 0.001) - } else { - // standing - const faceEnd = 8.75 - mesh.position.set(0, 0, (faceEnd - 16 / 2) / 16 + 0.001) - } - - const group = new THREE.Group() - group.rotation.set(0, -THREE.MathUtils.degToRad( - rotation * (isWall ? 90 : 45 / 2) - ), 0) - group.add(mesh) - const y = isWall ? 4.5 / 16 + mesh.scale.y / 2 : (1 - (mesh.scale.y / 2)) - group.position.set(position.x + 0.5, position.y + y, position.z + 0.5) - return group - } - - updateShowChunksBorder (value: boolean) { - this.showChunkBorders = value - for (const object of Object.values(this.sectionObjects)) { - for (const child of object.children) { - if (child.name === 'helper') { - child.visible = value - } - } - } - } - - resetWorld () { - // this.active = false - // for (const mesh of Object.values(this.sectionObjects)) { - // this.scene.remove(mesh) - // } - // this.sectionObjects = {} - // this.loadedChunks = {} - this.sectionsOutstanding = new Set() - for (const worker of this.workers) { - worker.postMessage({ type: 'reset' }) - } - } - - setVersion (version, texturesVersion = version) { - this.version = version - this.texturesVersion = texturesVersion - this.resetWorld() - this.active = true - - const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] - for (const worker of this.workers) { - const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) - mcData.version = JSON.parse(JSON.stringify(mcData.version)) - worker.postMessage({ type: 'mcData', mcData, version: this.version }) - } - - this.updateTexturesData() - } - - updateTexturesData () { - loadTexture(this.customTexturesDataUrl || `textures/${this.texturesVersion}.png`, (texture: import('three').Texture) => { - texture.magFilter = THREE.NearestFilter - texture.minFilter = THREE.NearestFilter - texture.flipY = false - this.material.map = texture - this.material.map.onUpdate = () => { - this.downloadedTextureImage = this.material.map!.image - } - const loadBlockStates = async () => { - return new Promise(resolve => { - if (this.customBlockStatesData) return resolve(this.customBlockStatesData) - return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { - this.downloadedBlockStatesData = data - // todo - this.renderUpdateEmitter.emit('blockStatesDownloaded') - resolve(data) - }) - }) - } - loadBlockStates().then((blockStates) => { - for (const worker of this.workers) { - worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: texture.image.width }) - } - }) - }) - - } - - getLoadedChunksRelative (pos: Vec3, includeY = false) { - const [currentX, currentY, currentZ] = sectionPos(pos) - return Object.fromEntries(Object.entries(this.sectionObjects).map(([key, o]) => { - const [xRaw, yRaw, zRaw] = key.split(',').map(Number) - const [x, y, z] = sectionPos({ x: xRaw, y: yRaw, z: zRaw }) - const setKey = includeY ? `${x - currentX},${y - currentY},${z - currentZ}` : `${x - currentX},${z - currentZ}` - return [setKey, o] - })) - } - - addColumn (x, z, chunk) { - this.initialChunksLoad = false - this.loadedChunks[`${x},${z}`] = true - for (const worker of this.workers) { - worker.postMessage({ type: 'chunk', x, z, chunk }) - } - for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { - const loc = new Vec3(x, y, z) - this.setSectionDirty(loc) - this.setSectionDirty(loc.offset(-16, 0, 0)) - this.setSectionDirty(loc.offset(16, 0, 0)) - this.setSectionDirty(loc.offset(0, 0, -16)) - this.setSectionDirty(loc.offset(0, 0, 16)) - } - } - - cleanChunkTextures (x, z) { - const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {} - for (const key of Object.keys(textures)) { - textures[key].dispose() - delete textures[key] - } - } - - removeColumn (x, z) { - for (const key of Object.keys(this.newChunks)) { - const [xSec, _ySec, zSec] = key.split(',').map(Number) - // if (Math.floor(x / 16) === x && Math.floor(z / 16) === z) { - if (x === xSec && z === zSec) { - // foundSections.push(key) - removeBlocksSection(key) - } - } - delete this.loadedChunks[`${x},${z}`] - for (const worker of this.workers) { - worker.postMessage({ type: 'unloadChunk', x, z }) - } - // for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { - // this.setSectionDirty(new Vec3(x, y, z), false) - // const key = `${x},${y},${z}` - // const mesh = this.sectionObjects[key] - // if (mesh) { - // this.scene.remove(mesh) - // dispose3(mesh) - // } - // delete this.sectionObjects[key] - // } - } - - setBlockStateId (pos, stateId) { - for (const worker of this.workers) { - worker.postMessage({ type: 'blockUpdate', pos, stateId }) - } - this.setSectionDirty(pos) - if ((pos.x & 15) === 0) this.setSectionDirty(pos.offset(-16, 0, 0)) - if ((pos.x & 15) === 15) this.setSectionDirty(pos.offset(16, 0, 0)) - if ((pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, 0)) - if ((pos.y & 15) === 15) this.setSectionDirty(pos.offset(0, 16, 0)) - if ((pos.z & 15) === 0) this.setSectionDirty(pos.offset(0, 0, -16)) - if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16)) - } - - setSectionDirty (pos, value = true) { - this.cleanChunkTextures(pos.x, pos.z) // todo don't do this! - // Dispatch sections to workers based on position - // This guarantees uniformity accross workers and that a given section - // is always dispatched to the same worker - const hash = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), this.workers.length) - this.workers[hash].postMessage({ type: 'dirty', x: pos.x, y: pos.y, z: pos.z, value }) - this.sectionsOutstanding.add(`${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`) - } - - // Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number - // of sections not rendered are 0 - async waitForChunksToRender () { - return new Promise((resolve, reject) => { - if ([...this.sectionsOutstanding].length === 0) { - resolve() - return - } - - const updateHandler = () => { - if (this.sectionsOutstanding.size === 0) { - this.renderUpdateEmitter.removeListener('update', updateHandler) - resolve() - } - } - this.renderUpdateEmitter.on('update', updateHandler) - }) - } -} diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts new file mode 100644 index 000000000..f43d3886e --- /dev/null +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -0,0 +1,203 @@ +import * as THREE from 'three' +import { Vec3 } from 'vec3' +import { loadTexture, loadJSON } from './utils' +import { EventEmitter } from 'events' +import mcDataRaw from 'minecraft-data/data.js'; // handled correctly in esbuild plugin +import { dynamicMcDataFiles } from '../../buildWorkerConfig.mjs' +import { toMajor } from './version.js' + +function mod (x, n) { + return ((x % n) + n) % n +} + +export abstract class WorldRendererCommon { + worldConfig = { minY: 0, worldHeight: 256 } + // todo @sa2urami set alphaTest back to 0.1 and instead properly sort transparent and solid objects (needs to be done in worker too) + material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.5 }) + + showChunkBorders = false + active = false + version = undefined as string | undefined + loadedChunks = {} as Record + sectionsOutstanding = new Set() + renderUpdateEmitter = new EventEmitter() + customBlockStatesData = undefined as any + customTexturesDataUrl = undefined as string | undefined + downloadedBlockStatesData = undefined as any + downloadedTextureImage = undefined as any + workers: any[] = [] + viewerPosition?: Vec3 + lastCamUpdate = 0 + droppedFpsPercentage = 0 + initialChunksLoad = true + enableChunksLoadDelay = false + texturesVersion?: string + // promisesQueue = [] as Promise[] + + constructor(numWorkers: number) { + // init workers + for (let i = 0; i < numWorkers; i++) { + // Node environment needs an absolute path, but browser needs the url of the file + let src = __dirname + if (typeof window === 'undefined') src += '/worker.js' + else src = 'worker.js' + + const worker: any = new Worker(src) + worker.onmessage = async ({ data }) => { + if (!this.active) return + this.handleWorkerMessage(data) + await new Promise(resolve => { + setTimeout(resolve, 0) + }) + if (data.type === 'sectionFinished') { + this.sectionsOutstanding.delete(data.key) + this.renderUpdateEmitter.emit('update') + } + } + if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) }) + this.workers.push(worker) + } + } + + abstract handleWorkerMessage (data: WorkerReceive): void + + /** + * Optionally update data that are depedendent on the viewer position + */ + abstract updatePosDataChunk (key: string): void + + updateViewerPosition (pos: Vec3) { + this.viewerPosition = pos + for (const [key, value] of Object.entries(this.loadedChunks)) { + if (!value) continue + this.updatePosDataChunk(key) + } + } + + sendWorkers (message: WorkerSend) { + for (const worker of this.workers) { + worker.postMessage(message) + } + } + + abstract updateShowChunksBorder (value: boolean): void + + resetWorld () { + this.active = false + this.loadedChunks = {} + this.sectionsOutstanding = new Set() + for (const worker of this.workers) { + worker.postMessage({ type: 'reset' }) + } + } + + setVersion (version, texturesVersion = version) { + this.version = version + this.texturesVersion = texturesVersion + this.resetWorld() + this.active = true + + const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] + for (const worker of this.workers) { + const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) + mcData.version = JSON.parse(JSON.stringify(mcData.version)) + worker.postMessage({ type: 'mcData', mcData, version: this.version }) + } + + this.updateTexturesData() + } + + updateTexturesData () { + loadTexture(this.customTexturesDataUrl || `textures/${this.texturesVersion}.png`, (texture: import('three').Texture) => { + texture.magFilter = THREE.NearestFilter + texture.minFilter = THREE.NearestFilter + texture.flipY = false + this.material.map = texture + this.material.map.onUpdate = () => { + this.downloadedTextureImage = this.material.map!.image + } + const loadBlockStates = async () => { + return new Promise(resolve => { + if (this.customBlockStatesData) return resolve(this.customBlockStatesData) + return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { + this.downloadedBlockStatesData = data + // todo + this.renderUpdateEmitter.emit('blockStatesDownloaded') + resolve(data) + }) + }) + } + loadBlockStates().then((blockStates) => { + for (const worker of this.workers) { + worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: texture.image.width }) + } + }) + }) + + } + + addColumn (x, z, chunk) { + this.initialChunksLoad = false + this.loadedChunks[`${x},${z}`] = true + for (const worker of this.workers) { + worker.postMessage({ type: 'chunk', x, z, chunk }) + } + for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { + const loc = new Vec3(x, y, z) + this.setSectionDirty(loc) + this.setSectionDirty(loc.offset(-16, 0, 0)) + this.setSectionDirty(loc.offset(16, 0, 0)) + this.setSectionDirty(loc.offset(0, 0, -16)) + this.setSectionDirty(loc.offset(0, 0, 16)) + } + } + + removeColumn (x, z) { + delete this.loadedChunks[`${x},${z}`] + for (const worker of this.workers) { + worker.postMessage({ type: 'unloadChunk', x, z }) + } + } + + setBlockStateId (pos, stateId) { + for (const worker of this.workers) { + worker.postMessage({ type: 'blockUpdate', pos, stateId }) + } + this.setSectionDirty(pos) + if ((pos.x & 15) === 0) this.setSectionDirty(pos.offset(-16, 0, 0)) + if ((pos.x & 15) === 15) this.setSectionDirty(pos.offset(16, 0, 0)) + if ((pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, 0)) + if ((pos.y & 15) === 15) this.setSectionDirty(pos.offset(0, 16, 0)) + if ((pos.z & 15) === 0) this.setSectionDirty(pos.offset(0, 0, -16)) + if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16)) + } + + setSectionDirty (pos, value = true) { + this.renderUpdateEmitter.emit('dirty', pos, value) + // Dispatch sections to workers based on position + // This guarantees uniformity accross workers and that a given section + // is always dispatched to the same worker + const hash = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), this.workers.length) + this.workers[hash].postMessage({ type: 'dirty', x: pos.x, y: pos.y, z: pos.z, value }) + this.sectionsOutstanding.add(`${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`) + } + + // Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number + // of sections not rendered are 0 + async waitForChunksToRender () { + return new Promise((resolve, reject) => { + if ([...this.sectionsOutstanding].length === 0) { + resolve() + return + } + + const updateHandler = () => { + if (this.sectionsOutstanding.size === 0) { + this.renderUpdateEmitter.removeListener('update', updateHandler) + resolve() + } + } + this.renderUpdateEmitter.on('update', updateHandler) + }) + } +} diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts new file mode 100644 index 000000000..553c2360a --- /dev/null +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -0,0 +1,214 @@ +import * as THREE from 'three' +import { Vec3 } from 'vec3' +import nbt from 'prismarine-nbt' +import { dispose3 } from './dispose' +import PrismarineChatLoader from 'prismarine-chat' +import { renderSign } from '../sign-renderer/' +import { chunkPos, sectionPos } from './simpleUtils' +import { WorldRendererCommon } from './worldrendererCommon' + +function mod (x, n) { + return ((x % n) + n) % n +} + +export class WorldRendererThree extends WorldRendererCommon { + blockEntities = {} + sectionObjects: Record = {} + showChunkBorders = false + chunkTextures = new Map() + signsCache = new Map() + + constructor(public scene: THREE.Scene, numWorkers = 4) { + super(numWorkers) + } + + /** + * Optionally update data that are depedendent on the viewer position + */ + updatePosDataChunk (key: string) { + if (!this.viewerPosition) return + const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16)) + const [xPlayer, yPlayer, zPlayer] = this.viewerPosition.toArray().map(x => Math.floor(x / 16)) + // sum of distances: x + y + z + const chunkDistance = Math.abs(x - xPlayer) + Math.abs(y - yPlayer) + Math.abs(z - zPlayer) + const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')! + section.renderOrder = 500 - chunkDistance + } + + handleWorkerMessage (data: any): void { + if (data.type !== 'geometry') return + let object: THREE.Object3D = this.sectionObjects[data.key] + if (object) { + this.scene.remove(object) + dispose3(object) + delete this.sectionObjects[data.key] + } + + const chunkCoords = data.key.split(',') + if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return + + // if (!this.initialChunksLoad && this.enableChunksLoadDelay) { + // const newPromise = new Promise(resolve => { + // if (this.droppedFpsPercentage > 0.5) { + // setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage) + // } else { + // setTimeout(resolve) + // } + // }) + // this.promisesQueue.push(newPromise) + // for (const promise of this.promisesQueue) { + // await promise + // } + // } + + const geometry = new THREE.BufferGeometry() + geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) + geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3)) + geometry.setAttribute('color', new THREE.BufferAttribute(data.geometry.colors, 3)) + geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2)) + geometry.setIndex(data.geometry.indices) + + const mesh = new THREE.Mesh(geometry, this.material) + mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) + mesh.name = 'mesh' + object = new THREE.Group() + object.add(mesh) + const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) + boxHelper.name = 'helper' + object.add(boxHelper) + object.name = 'chunk' + if (!this.showChunkBorders) { + boxHelper.visible = false + } + // should not compute it once + if (Object.keys(data.geometry.signs).length) { + for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { + const [x, y, z] = posKey.split(',') + const signBlockEntity = this.blockEntities[posKey] + if (!signBlockEntity) continue + const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity)); + if (!sign) continue + object.add(sign) + } + } + this.sectionObjects[data.key] = object + this.updatePosDataChunk(data.key) + this.scene.add(object) + } + + getSignTexture (position: Vec3, blockEntity, backSide = false) { + const chunk = chunkPos(position) + let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`) + if (!textures) { + textures = {} + this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures) + } + const texturekey = `${position.x},${position.y},${position.z}`; + // todo investigate bug and remove this so don't need to clean in section dirty + if (textures[texturekey]) return textures[texturekey] + + const PrismarineChat = PrismarineChatLoader(this.version!) + const canvas = renderSign(blockEntity, PrismarineChat) + if (!canvas) return + const tex = new THREE.Texture(canvas) + tex.magFilter = THREE.NearestFilter + tex.minFilter = THREE.NearestFilter + tex.needsUpdate = true + textures[texturekey] = tex + return tex + } + + renderSign (position: Vec3, rotation: number, isWall: boolean, blockEntity) { + const tex = this.getSignTexture(position, blockEntity) + + if (!tex) return + + // todo implement + // const key = JSON.stringify({ position, rotation, isWall }) + // if (this.signsCache.has(key)) { + // console.log('cached', key) + // } else { + // this.signsCache.set(key, tex) + // } + + const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true, })) + mesh.renderOrder = 999 + + // todo @sa2urami shouldnt all this be done in worker? + mesh.scale.set(1, 7 / 16, 1) + if (isWall) { + mesh.position.set(0, 0, -(8 - 1.5) / 16 + 0.001) + } else { + // standing + const faceEnd = 8.75 + mesh.position.set(0, 0, (faceEnd - 16 / 2) / 16 + 0.001) + } + + const group = new THREE.Group() + group.rotation.set(0, -THREE.MathUtils.degToRad( + rotation * (isWall ? 90 : 45 / 2) + ), 0) + group.add(mesh) + const y = isWall ? 4.5 / 16 + mesh.scale.y / 2 : (1 - (mesh.scale.y / 2)) + group.position.set(position.x + 0.5, position.y + y, position.z + 0.5) + return group + } + + updateShowChunksBorder (value: boolean) { + this.showChunkBorders = value + for (const object of Object.values(this.sectionObjects)) { + for (const child of object.children) { + if (child.name === 'helper') { + child.visible = value + } + } + } + } + + resetWorld () { + super.resetWorld() + + for (const mesh of Object.values(this.sectionObjects)) { + this.scene.remove(mesh) + } + } + + getLoadedChunksRelative (pos: Vec3, includeY = false) { + const [currentX, currentY, currentZ] = sectionPos(pos) + return Object.fromEntries(Object.entries(this.sectionObjects).map(([key, o]) => { + const [xRaw, yRaw, zRaw] = key.split(',').map(Number) + const [x, y, z] = sectionPos({ x: xRaw, y: yRaw, z: zRaw }) + const setKey = includeY ? `${x - currentX},${y - currentY},${z - currentZ}` : `${x - currentX},${z - currentZ}` + return [setKey, o] + })) + } + + cleanChunkTextures (x, z) { + const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {} + for (const key of Object.keys(textures)) { + textures[key].dispose() + delete textures[key] + } + } + + removeColumn (x, z) { + super.removeColumn(x, z) + + this.cleanChunkTextures(x, z) + for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { + this.setSectionDirty(new Vec3(x, y, z), false) + const key = `${x},${y},${z}` + const mesh = this.sectionObjects[key] + if (mesh) { + this.scene.remove(mesh) + dispose3(mesh) + } + delete this.sectionObjects[key] + } + } + + setSectionDirty (pos, value = true) { + this.cleanChunkTextures(pos.x, pos.z) // todo don't do this! + super.setSectionDirty(pos, value) + } +} diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts new file mode 100644 index 000000000..d999433b0 --- /dev/null +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -0,0 +1,59 @@ +import { addBlocksSection, removeBlocksSection } from '../../examples/webglRenderer' +import { WorldRendererCommon } from './worldrendererCommon' + +export class WorldRendererWebgl extends WorldRendererCommon { + hasWithFrames = undefined as number | undefined + newChunks = {} as Record + + constructor(numWorkers = 4) { + super(numWorkers) + } + + handleWorkerMessage (data: any): void { + if (data.type === 'geometry') { + + const chunkCoords = data.key.split(',') + if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return + + addBlocksSection(data.key, data.geometry) + const blocks = Object.values(data.geometry.blocks) as any[] + const animatedFrames = blocks.find((x: any) => { + return x.animatedFrames + }); + this.hasWithFrames = animatedFrames?.animatedFrames + this.newChunks[data.key] = data.geometry + } + } + + updatePosDataChunk (key: string) { + } + + + updateShowChunksBorder (value: boolean) { + // todo + } + + + removeColumn (x, z) { + super.removeColumn(x, z) + for (const key of Object.keys(this.newChunks)) { + const [xSec, _ySec, zSec] = key.split(',').map(Number) + // if (Math.floor(x / 16) === x && Math.floor(z / 16) === z) { + if (x === xSec && z === zSec) { + // foundSections.push(key) + removeBlocksSection(key) + } + } + // for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { + // this.setSectionDirty(new Vec3(x, y, z), false) + // const key = `${x},${y},${z}` + // const mesh = this.sectionObjects[key] + // if (mesh) { + // this.scene.remove(mesh) + // dispose3(mesh) + // } + // delete this.sectionObjects[key] + // } + } + +} From 12dfa9f9a84311bef7fdc01ba5fe36f7691f6fa2 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 6 Apr 2024 19:48:39 +0300 Subject: [PATCH 46/86] implement fully working animation in playground --- .../examples/TextureAnimation.ts | 69 +++++++++++++++++++ prismarine-viewer/examples/playground.ts | 43 +++++++++--- prismarine-viewer/examples/shared.ts | 4 ++ prismarine-viewer/globals.d.ts | 9 +++ prismarine-viewer/tsconfig.json | 15 ++-- prismarine-viewer/viewer/lib/models.ts | 10 +-- .../viewer/lib/worldrendererWebgl.ts | 26 +++++-- prismarine-viewer/viewer/prepare/atlas.ts | 18 +++-- .../viewer/prepare/generateTextures.ts | 8 ++- prismarine-viewer/viewer/prepare/webglData.ts | 28 ++++++++ 10 files changed, 198 insertions(+), 32 deletions(-) create mode 100644 prismarine-viewer/examples/TextureAnimation.ts create mode 100644 prismarine-viewer/examples/shared.ts create mode 100644 prismarine-viewer/globals.d.ts create mode 100644 prismarine-viewer/viewer/prepare/webglData.ts diff --git a/prismarine-viewer/examples/TextureAnimation.ts b/prismarine-viewer/examples/TextureAnimation.ts new file mode 100644 index 000000000..dfa004624 --- /dev/null +++ b/prismarine-viewer/examples/TextureAnimation.ts @@ -0,0 +1,69 @@ +export type AnimationControlSwitches = { + tick: number + interpolationTick: number // next one +} + +type Data = { + interpolate: boolean; + frametime: number; + frames: ({ + index: number; + time: number; + } | number)[] | undefined; +}; + +export class TextureAnimation { + data: Data; + frameImages: number; + frameDelta: number; + frameTime: number; + framesToSwitch: number; + frameIndex: number; + + constructor(public animationControl: AnimationControlSwitches, data: Data, public framesImages: number) { + this.data = { + interpolate: false, + frametime: 1, + ...data + }; + this.frameImages = 1; + this.frameDelta = 0; + this.frameTime = this.data.frametime * 50; + this.frameIndex = 0; + + this.framesToSwitch = this.frameImages; + if (this.data.frames) { + this.framesToSwitch = this.data.frames.length; + } + } + + step (deltaMs: number) { + this.frameDelta += deltaMs; + + if (this.frameDelta > this.frameTime) { + this.frameDelta -= this.frameTime; + this.frameDelta %= this.frameTime; + + this.frameIndex++; + this.frameIndex %= this.framesToSwitch; + + const frames = this.data.frames.map(frame => typeof frame === 'number' ? { index: frame, time: this.data.frametime } : frame); + if (frames) { + let frame = frames[this.frameIndex] + let nextFrame = frames[(this.frameIndex + 1) % this.framesToSwitch]; + + this.animationControl.tick = frame.index; + this.animationControl.interpolationTick = nextFrame.index; + this.frameTime = frame.time * 50; + } else { + this.animationControl.tick = this.frameIndex; + this.animationControl.interpolationTick = (this.frameIndex + 1) % this.framesToSwitch; + } + } + + if (this.data.interpolate) { + this.animationControl.interpolationTick = this.frameDelta / this.frameTime; + } + } + +} diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 5ceef5190..a5985bf45 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -21,6 +21,8 @@ globalThis.THREE = THREE //@ts-ignore import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { renderPlayground } from './TouchControls2' +import { WorldRendererWebgl } from '../viewer/lib/worldrendererWebgl' +import { TextureAnimation } from './TextureAnimation' const gui = new GUI() @@ -260,7 +262,7 @@ async function main () { const cameraPos = targetPos.offset(2, 2, 2) const pitch = THREE.MathUtils.degToRad(-45) const yaw = THREE.MathUtils.degToRad(45) - // viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX') + 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, cameraPos.y, cameraPos.z) // controls.update() @@ -297,6 +299,7 @@ async function main () { params.block ||= 'stone' + let textureAnimation: TextureAnimation | undefined const onUpdate = { block () { metadataFolder.destroy() @@ -365,7 +368,24 @@ async function main () { viewer.setBlockStateId(targetPos.offset(0, -1, 0), params.supportBlock ? 1 : 0) }, animationTick () { - setAnimationTick(params.animationTick, viewer.world.hasWithFrames - 1) + const webgl = (viewer.world as WorldRendererWebgl).playgroundGetWebglData() + if (!webgl?.animation) { + setAnimationTick(0) + return + } + if (params.animationTick === -1) { + textureAnimation = new TextureAnimation(new Proxy({} as any, { + set (t, p, v) { + if (p === 'tick') { + setAnimationTick(v) + } + return true + } + }), webgl.animation, webgl.animation.framesCount) + } else { + setAnimationTick(params.animationTick) + textureAnimation = undefined + } } } @@ -430,8 +450,15 @@ async function main () { window.requestAnimationFrame(animate2) } viewer.world.renderUpdateEmitter.addListener('update', () => { - const frames = viewer.world.hasWithFrames ? viewer.world.hasWithFrames - 1 : 0; - animationController.max(frames) + // const frames = viewer.world.hasWithFrames ? viewer.world.hasWithFrames - 1 : 0; + const webgl = (viewer.world as WorldRendererWebgl).playgroundGetWebglData() + if (webgl?.animation) { + params.animationTick = -1 + animationController.show() + animationController.max(webgl.animation.framesCount) + } else { + animationController.hide() + } onUpdate.animationTick() }) animate2() @@ -454,11 +481,11 @@ async function main () { // }) // #endregion + let time = performance.now() const continuousUpdate = () => { - if (continuousRender) { - animate() - } - // requestAnimationFrame(continuousUpdate) + textureAnimation?.step(performance.now() - time) + time = performance.now() + requestAnimationFrame(continuousUpdate) } continuousUpdate() diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts new file mode 100644 index 000000000..77ec6bda5 --- /dev/null +++ b/prismarine-viewer/examples/shared.ts @@ -0,0 +1,4 @@ +export type BlockType = { + textureIndex: number[] + textureName?: string +} diff --git a/prismarine-viewer/globals.d.ts b/prismarine-viewer/globals.d.ts new file mode 100644 index 000000000..c2cb5532a --- /dev/null +++ b/prismarine-viewer/globals.d.ts @@ -0,0 +1,9 @@ +type StringKeys = Extract + +interface ObjectConstructor { + keys (obj: T): Array> + entries (obj: T): Array<[StringKeys, T[keyof T]]> + // todo review https://stackoverflow.com/questions/57390305/trying-to-get-fromentries-type-right + fromEntries> (obj: T): Record + assign, K extends Record> (target: T, source: K): asserts target is T & K +} diff --git a/prismarine-viewer/tsconfig.json b/prismarine-viewer/tsconfig.json index 1cf48c6e3..421d76e33 100644 --- a/prismarine-viewer/tsconfig.json +++ b/prismarine-viewer/tsconfig.json @@ -1,9 +1,10 @@ { - "compilerOptions": { - "module": "commonjs", - "strictNullChecks": true - }, - "files": [ - "index.d.ts" - ] + "compilerOptions": { + "module": "commonjs", + "strictNullChecks": true + }, + "files": [ + "index.d.ts", + "globals.d.ts", + ] } diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 8f9d53ce4..f2a0ec9b3 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -469,14 +469,14 @@ export function getSectionGeometry (sx, sy, sz, world: World) { return element.faces } - let animatedFrames = undefined + let textureName = undefined const getResult = (side: string): number => { const facesOrTexture = findTextureInBlockStates(block.name); - if (!facesOrTexture) return + if (!facesOrTexture) return 0 // todo const result = 'u' in facesOrTexture ? facesOrTexture : facesOrTexture?.[side]?.texture if (!result) return 0 // todo - if (result.animatedFrames) { - animatedFrames = result.animatedFrames + if (result.textureName) { + textureName = result.textureName } return uvToTextureIndex(result.u, result.v) - (result.su < 0 ? 1 : 0) - (result.sv < 0 ? 1 : 0) } @@ -509,7 +509,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { if (textures.every(t => t === 0)) continue attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { textureIndex: textures, - animatedFrames + textureName } satisfies BlockType } diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts index d999433b0..9bd7ef20f 100644 --- a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -1,14 +1,26 @@ import { addBlocksSection, removeBlocksSection } from '../../examples/webglRenderer' +import type { WebglData } from '../prepare/webglData' +import { loadJSON } from './utils.web' import { WorldRendererCommon } from './worldrendererCommon' export class WorldRendererWebgl extends WorldRendererCommon { - hasWithFrames = undefined as number | undefined newChunks = {} as Record + webglData: WebglData constructor(numWorkers = 4) { super(numWorkers) } + playgroundGetWebglData () { + const playgroundChunk = Object.values(this.newChunks).filter((x: any) => Object.keys(x?.blocks ?? {}).length > 0)?.[0] as any + if (!playgroundChunk) return + const block = Object.values(playgroundChunk.blocks)?.[0] as any + if (!block) return + const { textureName } = block + if (!textureName) return + return this.webglData[textureName] + } + handleWorkerMessage (data: any): void { if (data.type === 'geometry') { @@ -16,11 +28,7 @@ export class WorldRendererWebgl extends WorldRendererCommon { if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return addBlocksSection(data.key, data.geometry) - const blocks = Object.values(data.geometry.blocks) as any[] - const animatedFrames = blocks.find((x: any) => { - return x.animatedFrames - }); - this.hasWithFrames = animatedFrames?.animatedFrames + // const blocks = Object.values(data.geometry.blocks) as any[] this.newChunks[data.key] = data.geometry } } @@ -28,6 +36,12 @@ export class WorldRendererWebgl extends WorldRendererCommon { updatePosDataChunk (key: string) { } + updateTexturesData (): void { + super.updateTexturesData() + loadJSON(`/webgl/${this.texturesVersion}.json`, (json) => { + this.webglData = json + }) + } updateShowChunksBorder (value: boolean) { // todo diff --git a/prismarine-viewer/viewer/prepare/atlas.ts b/prismarine-viewer/viewer/prepare/atlas.ts index 546f83bd7..8da53e0c3 100644 --- a/prismarine-viewer/viewer/prepare/atlas.ts +++ b/prismarine-viewer/viewer/prepare/atlas.ts @@ -31,11 +31,20 @@ export type JsonAtlas = { [file: string]: { u: number, v: number, + su?: number, + sv?: number, + animatedFrames?: number } } } -export const makeTextureAtlas = (input: string[], getInputData: (name) => { contents: string, tileWidthMult?: number }, tilesCount = input.length, suSvOptimize: 'remove' | null = null): { +export const makeTextureAtlas = ( + input: string[], + getInputData: (name) => { contents: string, tileWidthMult?: number }, + tilesCount = input.length, + suSvOptimize: 'remove' | null = null, + renderAnimated = true +): { image: Buffer, canvas: Canvas, json: JsonAtlas @@ -47,7 +56,7 @@ export const makeTextureAtlas = (input: string[], getInputData: (name) => { cont const canvas = new Canvas(imgSize, imgSize, 'png' as any) const g = canvas.getContext('2d') - const texturesIndex = {} + const texturesIndex = {} as JsonAtlas['textures'] let offset = 0 const suSv = tileSize / imgSize @@ -63,12 +72,12 @@ export const makeTextureAtlas = (input: string[], getInputData: (name) => { cont const renderWidth = tileSize * (inputData.tileWidthMult ?? 1) let animatedFrames = 0 const addDebugText = (x, y) => { - // return // disable debug text + return // disable debug text g.fillStyle = 'black' g.font = '8px Arial' g.fillText(i, x, y) } - if (img.height > tileSize) { + if (img.height > tileSize && renderAnimated) { const frames = img.height / tileSize; animatedFrames = frames console.log("Animated texture", keyValue, frames) @@ -92,6 +101,7 @@ export const makeTextureAtlas = (input: string[], getInputData: (name) => { cont su: suSv, sv: suSv }, + textureName: cleanName, animatedFrames: animatedFrames || undefined } } diff --git a/prismarine-viewer/viewer/prepare/generateTextures.ts b/prismarine-viewer/viewer/prepare/generateTextures.ts index 93f7a52ca..2c0e3902d 100644 --- a/prismarine-viewer/viewer/prepare/generateTextures.ts +++ b/prismarine-viewer/viewer/prepare/generateTextures.ts @@ -5,11 +5,12 @@ import mcAssets from 'minecraft-assets' import fs from 'fs-extra' import { prepareMoreGeneratedBlocks } from './moreGeneratedBlocks' import { generateItemsAtlases } from './genItemsAtlas' +import { prepareWebglData } from './webglData' const publicPath = path.resolve(__dirname, '../../public') const texturesPath = path.join(publicPath, 'textures') -if (fs.existsSync(texturesPath) && !process.argv.includes('-f')) { +if (fs.existsSync(texturesPath) && !process.argv.includes('-f') && !process.argv.includes('-l')) { console.log('textures folder already exists, skipping...') process.exit(0) } @@ -17,12 +18,13 @@ fs.mkdirSync(texturesPath, { recursive: true }) const blockStatesPath = path.join(publicPath, 'blocksStates') fs.mkdirSync(blockStatesPath, { recursive: true }) +fs.mkdirSync(path.join(publicPath, 'webgl'), { recursive: true }) const warnings = new Set() Promise.resolve().then(async () => { generateItemsAtlases() console.time('generateTextures') - const versions = process.argv.includes('-l') ? mcAssets.versions.at(-1) : mcAssets.versions + const versions = process.argv.includes('-l') ? [mcAssets.versions.at(-1)!] : mcAssets.versions for (const version of versions as typeof mcAssets['versions']) { // for debugging (e.g. when above is overridden) if (!versions.includes(version)) { @@ -41,6 +43,8 @@ Promise.resolve().then(async () => { const blocksStates = JSON.stringify(prepareBlocksStates(assets, atlas)) fs.writeFileSync(path.resolve(blockStatesPath, version + '.json'), blocksStates) + const webglData = prepareWebglData(path.join(assets.directory, 'blocks'), atlas.json) + fs.writeFileSync(path.resolve(publicPath, 'webgl', version + '.json'), JSON.stringify(webglData)) fs.copySync(assets.directory, path.resolve(texturesPath, version), { overwrite: true }) } diff --git a/prismarine-viewer/viewer/prepare/webglData.ts b/prismarine-viewer/viewer/prepare/webglData.ts new file mode 100644 index 000000000..937637e36 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/webglData.ts @@ -0,0 +1,28 @@ +import { JsonAtlas } from './atlas'; +import { join } from 'path' +import fs from 'fs' + +export type WebglData = ReturnType + +export const prepareWebglData = (blockTexturesDir: string, atlas: JsonAtlas) => { + // todo + return Object.fromEntries(Object.entries(atlas.textures).map(([texture, { animatedFrames }]) => { + if (!animatedFrames) return null! + const mcMeta = JSON.parse(fs.readFileSync(join(blockTexturesDir, texture + '.png.mcmeta'), 'utf8')) as { + animation: { + interpolate: boolean, + frametime: number, + frames: ({ + index: number, + time: number + } | number)[] + } + } + return [texture, { + animation: { + ...mcMeta.animation, + framesCount: animatedFrames + } + }] as const + }).filter(Boolean)) +} From 4c58f4538e0ed535404d628df51d99e2f7dc1f5e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 6 Apr 2024 19:57:06 +0300 Subject: [PATCH 47/86] copy webgl data! --- scripts/build.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/build.js b/scripts/build.js index 9840e5d3e..61c09b20c 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -13,6 +13,7 @@ const entityMcAssets = McAssets('1.16.4') // these files could be copied at build time eg with copy plugin, but copy plugin slows down the config so we copy them there, alternative we could inline it in esbuild config const filesToCopy = [ { from: `${prismarineViewerBase}/public/blocksStates/`, to: 'dist/blocksStates/' }, + { from: `${prismarineViewerBase}/public/webgl/`, to: 'dist/webgl/' }, { from: `${prismarineViewerBase}/public/worker.js`, to: 'dist/worker.js' }, { from: './assets/', to: './dist/' }, { from: './config.json', to: 'dist/config.json' }, @@ -92,7 +93,7 @@ exports.getSwAdditionalEntries = () => { } exports.moveStorybookFiles = () => { - fsExtra.moveSync('storybook-static', 'dist/storybook', {overwrite: true,}) + fsExtra.moveSync('storybook-static', 'dist/storybook', { overwrite: true, }) fsExtra.copySync('dist/storybook', '.vercel/output/static/storybook') } From 9fe0e340f802bf5d63533ecd7c7880e5f00883f6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 6 Apr 2024 23:55:45 +0300 Subject: [PATCH 48/86] minor render tweaks --- prismarine-viewer/examples/webglRendererWorker.ts | 4 ++-- prismarine-viewer/viewer/lib/models.ts | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 165e94c64..dd9289ae5 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -84,7 +84,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im -0.5, 0.5, -0.5, 0.0, 1.0, 5.0// top-let ]) - let NumberOfCube = isPlayground ? 1_500_000 : 5_000_000 + let NumberOfCube = isPlayground ? 1_000_000 : 5_000_000 cubePositions = new Float32Array(NumberOfCube * 3) let cubeTextureIndices = new Float32Array(NumberOfCube * 6); @@ -172,7 +172,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // [camera.position.x, camera.position.y, camera.position.z - 2, 'dirt'], // ] const blocks = allBlocks.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) - globalThis.allBlocksSize = allBlocks.length / 3 + globalThis.allBlocksSize = allBlocks.length cubePositions = new Float32Array(blocks.length * 3) cubeTextureIndices = new Float32Array(blocks.length * 6); for (let i = 0; i < blocks.length * 3; i += 3) { diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index f2a0ec9b3..4302bcd98 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -419,7 +419,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { block.variant = getModelVariants(block) } - if (/* block.name !== 'water' && block.name !== 'lava' *//* && block.isCube */block.name !== 'air') { + if (block.name !== 'water' && /* && block.name !== 'lava' *//* && block.isCube */block.name !== 'air') { let globalMatrix = null as any let globalShift = null as any @@ -503,10 +503,11 @@ export function getSectionGeometry (sx, sy, sz, world: World) { getResult('south'), getResult('west'), getResult('east'), - getResult('up'), - getResult('down') + getResult('down'), + getResult('up') ] if (textures.every(t => t === 0)) continue + if (pos.y <= 1) continue // TODO!! attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { textureIndex: textures, textureName From 17cd4bff773a99e8f93c6b1da686820d414a536d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 7 Apr 2024 01:13:03 +0300 Subject: [PATCH 49/86] fix critical texture index bug --- prismarine-viewer/examples/webglRendererWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index dd9289ae5..55a4d72d5 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -157,7 +157,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.enableVertexAttribArray(5); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); gl.vertexAttribPointer(4, 4, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(5, 2, gl.FLOAT, false, 2 * 6, 4 * 4); + gl.vertexAttribPointer(5, 2, gl.FLOAT, false, 4 * 6, 4 * 4); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(4, 1); gl.vertexAttribDivisor(5, 1); From 183f8e461e4379e641939f344c82c811f732254e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Sun, 7 Apr 2024 01:34:21 +0300 Subject: [PATCH 50/86] Fix texture buffer data type and usage in webglRendererWorker.ts --- prismarine-viewer/examples/_FragmentShader.frag | 2 -- prismarine-viewer/examples/webglRendererWorker.ts | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index 14d6fde78..6560088ce 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -17,7 +17,5 @@ void main() coord = coord + ivec2(TexCoord * 16.0f); vec4 t = texelFetch(texture1, coord, 0); - if (t.z < 0.01) - discard; FragColor = t; } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 55a4d72d5..5a490091b 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -121,11 +121,11 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im let instanceTextureID = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.STATIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.DYNAMIC_DRAW); // todo gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.STATIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.DYNAMIC_DRAW); // todo gl.bindBuffer(gl.ARRAY_BUFFER, null); VAO = gl.createVertexArray(); let VBO = gl.createBuffer(); @@ -254,7 +254,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, textureWidth, textureHeight, 0, gl.RGB, gl.UNSIGNED_BYTE, textureBitmap); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, textureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureBitmap); //gl.generateMipmap(gl.TEXTURE_2D); //gl.generateMipmap(gl.TEXTURE_2D); From 7777307c4f10c5fab964dc65939f900d3d8dc007 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 7 Apr 2024 01:48:43 +0300 Subject: [PATCH 51/86] fix critical performance scene render --- prismarine-viewer/examples/playground.ts | 4 ++++ prismarine-viewer/examples/webglRenderer.ts | 8 +++----- prismarine-viewer/viewer/lib/viewer.ts | 4 ++-- prismarine-viewer/viewer/lib/worldrendererWebgl.ts | 7 +++++++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index a5985bf45..bbd038a8f 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -138,11 +138,15 @@ async function main () { return chunk }) + let stopUpdate = false + // let stopUpdate = true + // await schem.paste(world, new Vec3(0, 60, 0)) const worldView = new WorldDataEmitter(world, viewDistance, targetPos) const nullRenderer = new THREE.WebGLRenderer({ antialias: true }) const viewer = new Viewer(nullRenderer, 1) + viewer.world.stopBlockUpdate = stopUpdate viewer.setVersion(version) globalThis.viewer = viewer diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index adb5b9759..c6b5b2afc 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -31,15 +31,13 @@ if (typeof customEvents !== 'undefined') { let isWaitingToUpload = false export const addBlocksSection = (key, data) => { + sendWorkerMessage({ + type: 'addBlocksSection', data, key + }) if (isWaitingToUpload) return isWaitingToUpload = true viewer.waitForChunksToRender().then(() => { isWaitingToUpload = false - for (const [key, data] of Object.entries(viewer.world.newChunks)) { - sendWorkerMessage({ - type: 'addBlocksSection', data, key - }) - } if (allReceived || (true && Object.values(viewer.world.newChunks).length)) { sendWorkerMessage({ type: 'addBlocksSectionDone' diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 5a457c297..57f5ee5e5 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -15,7 +15,7 @@ export class Viewer { ambientLight: THREE.AmbientLight directionalLight: THREE.DirectionalLight camera: THREE.PerspectiveCamera - world: WorldRendererWebgl | WorldRendererThree + world: WorldRendererWebgl/* | WorldRendererThree */ entities: Entities primitives: Primitives domElement: HTMLCanvasElement @@ -163,7 +163,7 @@ export class Viewer { }) // todo remove and use other architecture instead so data flow is clear emitter.on('blockEntities', (blockEntities) => { - (this.world as WorldRendererThree).blockEntities = blockEntities + (this.world as unknown as WorldRendererThree).blockEntities = blockEntities }) emitter.on('unloadChunk', ({ x, z }) => { diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts index 9bd7ef20f..4556d953c 100644 --- a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -6,6 +6,7 @@ import { WorldRendererCommon } from './worldrendererCommon' export class WorldRendererWebgl extends WorldRendererCommon { newChunks = {} as Record webglData: WebglData + stopBlockUpdate = false constructor(numWorkers = 4) { super(numWorkers) @@ -21,6 +22,11 @@ export class WorldRendererWebgl extends WorldRendererCommon { return this.webglData[textureName] } + setBlockStateId (pos: any, stateId: any): void { + if (this.stopBlockUpdate) return + super.setBlockStateId(pos, stateId) + } + handleWorkerMessage (data: any): void { if (data.type === 'geometry') { @@ -49,6 +55,7 @@ export class WorldRendererWebgl extends WorldRendererCommon { removeColumn (x, z) { + return super.removeColumn(x, z) for (const key of Object.keys(this.newChunks)) { const [xSec, _ySec, zSec] = key.split(',').map(Number) From 31b419c6ee055475931af47e50a2a3a1601815b2 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 7 Apr 2024 02:03:46 +0300 Subject: [PATCH 52/86] add tint --- prismarine-viewer/examples/shared.ts | 1 + .../examples/webglRendererWorker.ts | 2 +- prismarine-viewer/viewer/lib/models.ts | 25 +++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts index 77ec6bda5..21fe635d9 100644 --- a/prismarine-viewer/examples/shared.ts +++ b/prismarine-viewer/examples/shared.ts @@ -1,4 +1,5 @@ export type BlockType = { textureIndex: number[] textureName?: string + tint?: [number, number, number] } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 5a490091b..98dbf87ef 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -316,7 +316,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im camera.updateMatrix() if (!globalThis.stopRendering) { - gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, allBlocks.length || NumberOfCube); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, isPlayground ? NumberOfCube : allBlocks.length); } //gl.bindVertexArray(null) diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 4302bcd98..1638cd234 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -470,10 +470,30 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } let textureName = undefined + let tint const getResult = (side: string): number => { const facesOrTexture = findTextureInBlockStates(block.name); if (!facesOrTexture) return 0 // todo - const result = 'u' in facesOrTexture ? facesOrTexture : facesOrTexture?.[side]?.texture + let result + if ('u' in facesOrTexture) { + result = facesOrTexture + } else { + result = facesOrTexture?.[side]?.texture + const tintindex = facesOrTexture?.[side]?.tintindex + if (tintindex === 0) { + if (block.name === 'redstone_wire') { + tint = tints.redstone[`${block.getProperties().power}`] + } else if (block.name === 'birch_leaves' || + block.name === 'spruce_leaves' || + block.name === 'lily_pad') { + tint = tints.constant[block.name] + } else if (block.name.includes('leaves') || block.name === 'vine') { + tint = tints.foliage[biome] + } else { + tint = tints.grass[biome] + } + } + } if (!result) return 0 // todo if (result.textureName) { textureName = result.textureName @@ -510,7 +530,8 @@ export function getSectionGeometry (sx, sy, sz, world: World) { if (pos.y <= 1) continue // TODO!! attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { textureIndex: textures, - textureName + textureName, + tint } satisfies BlockType } From 9f89af613fbda9dce52aff774b57b21b101b0e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Sun, 7 Apr 2024 02:37:21 +0300 Subject: [PATCH 53/86] Add biome color support to fragment shader --- .../examples/_FragmentShader.frag | 12 ++++++- prismarine-viewer/examples/_VertexShader.vert | 4 +++ .../examples/webglRendererWorker.ts | 33 ++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/examples/_FragmentShader.frag b/prismarine-viewer/examples/_FragmentShader.frag index 6560088ce..ca0c9f4dc 100644 --- a/prismarine-viewer/examples/_FragmentShader.frag +++ b/prismarine-viewer/examples/_FragmentShader.frag @@ -5,6 +5,7 @@ out vec4 FragColor; in vec2 TexCoord; flat in float TextureIndex; +flat in vec3 BiomeColor; uniform sampler2D texture1; @@ -17,5 +18,14 @@ void main() coord = coord + ivec2(TexCoord * 16.0f); vec4 t = texelFetch(texture1, coord, 0); - FragColor = t; + if (abs(t.x-t.y) <=0.010 || abs(t.x-t.z)<=0.010 ||abs(t.y-t.z) <=0.010) + { + FragColor = vec4(BiomeColor,1.0f)*t; + } + else + { + FragColor = t; + //FragColor = mix(t, vec4(BiomeColor, 1.0f), 0.5f); + } + } diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index 4d120e51b..f1f6b0748 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -6,11 +6,13 @@ layout (location = 2) in float CubeSide; layout (location = 3) in vec3 aOffset; layout (location = 4) in vec4 aTextureIndex; layout (location = 5) in vec2 aTextureIndexPlus; +layout (location = 6) in vec3 aBiomeColor; out vec2 TexCoord; flat out float TextureIndex; +flat out vec3 BiomeColor; uniform mat4 view; uniform mat4 projection; @@ -46,6 +48,8 @@ void main() } TextureIndex += float(tick); + BiomeColor = aBiomeColor; + //CubeSideIndex = CubeSide; //Passing cube side index to fragment shader } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 98dbf87ef..1319a41c7 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -88,6 +88,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im cubePositions = new Float32Array(NumberOfCube * 3) let cubeTextureIndices = new Float32Array(NumberOfCube * 6); + let cubeBiomeColor = new Float32Array(NumberOfCube * 3); // write random coordinates to cube positions xyz ten cubes; @@ -96,6 +97,9 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im cubePositions[i] = Math.floor(Math.random() * 1000) - 500; cubePositions[i + 1] = Math.floor(Math.random() * 1000) - 500; cubePositions[i + 2] = Math.floor(Math.random() * 100) - 100; + cubeBiomeColor[i] = (Math.random() ) ; + cubeBiomeColor[i + 1] = (Math.random() ) ; + cubeBiomeColor[i + 2] = (Math.random() ) ; // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); // cubeTextureIndices[i / 3] = 0; } @@ -119,6 +123,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im let VAO = gl.createVertexArray(); let instanceVBO = gl.createBuffer(); let instanceTextureID = gl.createBuffer(); + let instanceBiomeColor = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.DYNAMIC_DRAW); // todo @@ -127,6 +132,10 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.DYNAMIC_DRAW); // todo gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); + gl.bufferData(gl.ARRAY_BUFFER, cubeBiomeColor, gl.DYNAMIC_DRAW); // todo + gl.bindBuffer(gl.ARRAY_BUFFER, null); VAO = gl.createVertexArray(); let VBO = gl.createBuffer(); // let VBO_sides = gl.createBuffer(); @@ -162,6 +171,12 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.vertexAttribDivisor(4, 1); gl.vertexAttribDivisor(5, 1); + gl.enableVertexAttribArray(6); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); + gl.vertexAttribPointer(6, 3, gl.FLOAT, false, 3 * 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(6, 1); + updateCubes = (startIndex) => { // cubePositionsRaw = [ // // for now one cube in front of the camera @@ -175,10 +190,22 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im globalThis.allBlocksSize = allBlocks.length cubePositions = new Float32Array(blocks.length * 3) cubeTextureIndices = new Float32Array(blocks.length * 6); + cubeBiomeColor = new Float32Array(blocks.length * 3); for (let i = 0; i < blocks.length * 3; i += 3) { cubePositions[i] = blocks[i / 3][0] cubePositions[i + 1] = blocks[i / 3][1] cubePositions[i + 2] = blocks[i / 3][2] + const block = blocks[i / 3][3] as BlockType + if (block.tint) { + const [r, g, b] = block.tint + cubeBiomeColor[i] = r + cubeBiomeColor[i + 1] = g + cubeBiomeColor[i + 2] = b + } else { + cubeBiomeColor[i] = 1 + cubeBiomeColor[i + 1] = 1 + cubeBiomeColor[i + 2] = 1 + } } for (let i = 0; i < blocks.length * 6; i += 6) { @@ -213,6 +240,10 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im const TEXTURES_SIZE = 6 gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * TEXTURES_SIZE, cubeTextureIndices); // update buffer content gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); + gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * POS_SIZE, cubeBiomeColor); // update buffer content + gl.bindBuffer(gl.ARRAY_BUFFER, null); } globalThis.updateCubes = updateCubes @@ -304,7 +335,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im } gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) - gl.clearColor(0.5, 0.5, 0.5, 0.0); + gl.clearColor( 0.6784313725490196, 0.8470588235294118, 0.9019607843137255 , 0.0); gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.DEPTH_BUFFER_BIT) From db722dba34cb5b78236348c1f8742b98047e0ebc Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 25 Mar 2024 08:19:56 +0300 Subject: [PATCH 54/86] fix vercel preview deploy --- .github/workflows/preview.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index ef1d77737..2a5f56659 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -21,6 +21,7 @@ jobs: uses: actions/checkout@v2 with: ref: refs/pull/${{ github.event.issue.number }}/head + - run: npm i -g pnpm - uses: actions/setup-node@v4 with: node-version: 18 From c4d65ee9fdcb8fd1e26df92691a4736a9e13d627 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 25 Mar 2024 09:57:26 +0300 Subject: [PATCH 55/86] fix warps display for greenfield & click action --- src/loadSave.ts | 6 ++++-- src/react/Notification.tsx | 4 ++-- src/react/NotificationProvider.tsx | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/loadSave.ts b/src/loadSave.ts index 965b1a7c6..a707da4d4 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -6,7 +6,7 @@ import { proxy } from 'valtio' import { gzip } from 'node-gzip' import { options } from './optionsStorage' import { nameToMcOfflineUUID, disconnect } from './flyingSquidUtils' -import { forceCachedDataPaths, forceRedirectPaths, mkdirRecursive } from './browserfs' +import { existsViaStats, forceCachedDataPaths, forceRedirectPaths, mkdirRecursive } from './browserfs' import { isMajorVersionGreater } from './utils' import { activeModalStacks, insertActiveModalStack, miscUiState } from './globalState' @@ -154,7 +154,9 @@ export const loadSave = async (root = '/world') => { // improve compatibility with community saves const rootRemapFiles = ['Warp files'] for (const rootRemapFile of rootRemapFiles) { - forceRedirectPaths[path.join(root, rootRemapFile)] = path.join(root, '..', rootRemapFile) + if (await existsViaStats(path.join(root, '..', rootRemapFile))) { + forceRedirectPaths[path.join(root, rootRemapFile)] = path.join(root, '..', rootRemapFile) + } } // todo reimplement diff --git a/src/react/Notification.tsx b/src/react/Notification.tsx index 6ebd49e26..0f3a63259 100644 --- a/src/react/Notification.tsx +++ b/src/react/Notification.tsx @@ -36,7 +36,7 @@ export default ({ type = 'message', message, subMessage = '', open, icon = '', a position: 'fixed', top: 0, right: 0, - width: '160px', + width: '155px', whiteSpace: 'nowrap', fontSize: '9px', display: 'flex', @@ -45,7 +45,7 @@ export default ({ type = 'message', message, subMessage = '', open, icon = '', a padding: '3px 8px', background: isError ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.7)', borderRadius: '0 0 0 5px', - pointerEvents: action ? 'auto' : 'none', + pointerEvents: action ? '' : 'none', zIndex: 1200, // even above stats ...addStyles }}> diff --git a/src/react/NotificationProvider.tsx b/src/react/NotificationProvider.tsx index 2f27a595f..e5ceb0b36 100644 --- a/src/react/NotificationProvider.tsx +++ b/src/react/NotificationProvider.tsx @@ -40,7 +40,7 @@ export const hideNotification = () => { } export default () => { - const { autoHide, message, open, icon, type, subMessage } = useSnapshot(notificationProxy) + const { autoHide, message, open, icon, type, subMessage, action } = useSnapshot(notificationProxy) useEffect(() => { if (autoHide && open) { @@ -58,6 +58,7 @@ export default () => { // }, []) return Date: Mon, 25 Mar 2024 13:47:01 +0300 Subject: [PATCH 56/86] three shake three.js (#94) --- scripts/esbuildPlugins.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index 07938c6ec..95d88b8ed 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -27,7 +27,8 @@ const plugins = [ build.onLoad({ filter: /minecraft-data[\/\\]data.js$/, }, (args) => { - const version = supportedVersions.at(-1); + const version = supportedVersions.at(-1) + if (!version) throw new Error('unreachable') const data = MCData(version) const defaultVersionsObj = { // default protocol data, needed for auto-version @@ -47,6 +48,14 @@ const plugins = [ }, () => { throw new Error('hit banned package') }) + + build.onResolve({ + filter: /^three$/, + }, async ({ kind, resolveDir }) => { + return { + path: (await build.resolve('three/src/Three.js', { kind, resolveDir })).path, + } + }) } }, { From 48d3bbeef5be50be394af44f31052069cf3f9e06 Mon Sep 17 00:00:00 2001 From: gguio <109200692+gguio@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:00:11 +0400 Subject: [PATCH 57/86] feat: effects + system indicators in hud! (#95) Co-authored-by: gguio --- src/browserfs.ts | 24 ++++- src/globalState.ts | 1 + src/index.ts | 2 + src/loadSave.ts | 4 +- src/react/IndicatorEffects.css | 35 ++++++++ src/react/IndicatorEffects.stories.tsx | 35 ++++++++ src/react/IndicatorEffects.tsx | 111 +++++++++++++++++++++++ src/react/IndicatorEffectsProvider.tsx | 118 +++++++++++++++++++++++++ src/react/effectsImages.ts | 76 ++++++++++++++++ src/reactUi.tsx | 2 + src/utils.ts | 6 ++ 11 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 src/react/IndicatorEffects.css create mode 100644 src/react/IndicatorEffects.stories.tsx create mode 100644 src/react/IndicatorEffects.tsx create mode 100644 src/react/IndicatorEffectsProvider.tsx create mode 100644 src/react/effectsImages.ts diff --git a/src/browserfs.ts b/src/browserfs.ts index 42159b490..ebe8acfdd 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -60,7 +60,18 @@ fs.promises = new Proxy(Object.fromEntries(['readFile', 'writeFile', 'stat', 'mk if (p === 'open' && fsState.isReadonly) { args[1] = 'r' // read-only, zipfs throw otherwise } - return target[p](...args) + if (p === 'readFile') { + fsState.openReadOperations++ + } else if (p === 'writeFile') { + fsState.openWriteOperations++ + } + return target[p](...args).finally(() => { + if (p === 'readFile') { + fsState.openReadOperations-- + } else if (p === 'writeFile') { + fsState.openWriteOperations-- + } + }) } } }) @@ -77,7 +88,18 @@ fs.promises.open = async (...args) => { return } + if (x === 'read') { + fsState.openReadOperations++ + } else if (x === 'write' || x === 'close') { + fsState.openWriteOperations++ + } fs[x](fd, ...args, (err, bytesRead, buffer) => { + if (x === 'read') { + fsState.openReadOperations-- + } else if (x === 'write' || x === 'close') { + // todo that's not correct + fsState.openWriteOperations-- + } if (err) throw err // todo if readonly probably there is no need to open at all (return some mocked version - check reload)? if (x === 'write' && !fsState.isReadonly) { diff --git a/src/globalState.ts b/src/globalState.ts index 749b3a8fc..392d5fd9c 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -129,6 +129,7 @@ export type AppConfig = { export const miscUiState = proxy({ currentDisplayQr: null as string | null, currentTouch: null as boolean | null, + hasErrors: false, singleplayer: false, flyingSquid: false, wanOpened: false, diff --git a/src/index.ts b/src/index.ts index 1ee933915..aaa4989c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -258,6 +258,7 @@ async function connect (connectOptions: { server?: string; singleplayer?: any; username: string; password?: any; proxy?: any; botVersion?: any; serverOverrides?; serverOverridesFlat?; peerId?: string }) { if (miscUiState.gameLoaded) return + miscUiState.hasErrors = false lastConnectOptions.value = connectOptions document.getElementById('play-screen').style = 'display: none;' removePanorama() @@ -318,6 +319,7 @@ async function connect (connectOptions: { console.error(err) errorAbortController.abort() if (isCypress()) throw err + miscUiState.hasErrors = true if (miscUiState.gameLoaded) return setLoadingScreenStatus(`Error encountered. ${err}`, true) diff --git a/src/loadSave.ts b/src/loadSave.ts index a707da4d4..655fbea88 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -17,7 +17,9 @@ export const fsState = proxy({ isReadonly: false, syncFs: false, inMemorySave: false, - saveLoaded: false + saveLoaded: false, + openReadOperations: 0, + openWriteOperations: 0, }) const PROPOSE_BACKUP = true diff --git a/src/react/IndicatorEffects.css b/src/react/IndicatorEffects.css new file mode 100644 index 000000000..2902bfeed --- /dev/null +++ b/src/react/IndicatorEffects.css @@ -0,0 +1,35 @@ +.effectsScreen-container { + position: fixed; + top: 6%; + left: 0px; + z-index: -1; + pointer-events: none; +} + +.indicators-container { + display: flex; + font-size: 0.7em; +} + +.effects-container { + display: flex; + flex-direction: column; +} + +.effect-box { + display: flex; + align-items: center; +} + +.effect-box__image { + width: 23px; + margin-right: 3px; +} + +.effect-box__time { + font-size: 0.65rem; +} + +.effect-box__level { + font-size: 0.45rem; +} diff --git a/src/react/IndicatorEffects.stories.tsx b/src/react/IndicatorEffects.stories.tsx new file mode 100644 index 000000000..455718fe0 --- /dev/null +++ b/src/react/IndicatorEffects.stories.tsx @@ -0,0 +1,35 @@ +import 'iconify-icon' + +import type { Meta, StoryObj } from '@storybook/react' + +import IndicatorEffects, { defaultIndicatorsState } from './IndicatorEffects' +import { images } from './effectsImages' + +const meta: Meta = { + component: IndicatorEffects +} + +export default meta +type Story = StoryObj; + +export const Primary: Story = { + args: { + indicators: defaultIndicatorsState, + effects: [ + { + image: images.glowing, + time: 200, + level: 255, + removeEffect (image: string) {}, + reduceTime (image: string) {} + }, + { + image: images.absorption, + time: 30, + level: 99, + removeEffect (image: string) {}, + reduceTime (image: string) {} + } + ], + } +} diff --git a/src/react/IndicatorEffects.tsx b/src/react/IndicatorEffects.tsx new file mode 100644 index 000000000..b767cc237 --- /dev/null +++ b/src/react/IndicatorEffects.tsx @@ -0,0 +1,111 @@ +import { useMemo, useEffect, useRef } from 'react' +import PixelartIcon from './PixelartIcon' +import './IndicatorEffects.css' + + + +function formatTime (seconds: number): string { + if (seconds < 0) return '' + const minutes = Math.floor(seconds / 60) + const remainingSeconds = seconds % 60 + const formattedMinutes = String(minutes).padStart(2, '0') + const formattedSeconds = String(remainingSeconds).padStart(2, '0') + return `${formattedMinutes}:${formattedSeconds}` +} + +export type EffectType = { + image: string, + time: number, + level: number, + removeEffect: (image: string) => void, + reduceTime: (image: string) => void +} + +const EffectBox = ({ image, time, level }: Pick) => { + + const formattedTime = useMemo(() => formatTime(time), [time]) + + return
+ +
+ {formattedTime ? ( + // if time is negative then effect is shown without time. + // Component should be removed manually with time = 0 +
{formattedTime}
+ ) : null } + {level > 0 && level < 256 ? ( +
{level + 1}
+ ) : null } +
+
+} + +export const defaultIndicatorsState = { + chunksLoading: false, + readingFiles: false, + readonlyFiles: false, + writingFiles: false, // saving + appHasErrors: false, +} + +const indicatorIcons: Record = { + chunksLoading: 'add-grid', + readingFiles: 'arrow-bar-down', + writingFiles: 'arrow-bar-up', + appHasErrors: 'alert', + readonlyFiles: 'file-off', +} + +export default ({ indicators, effects }: {indicators: typeof defaultIndicatorsState, effects: readonly EffectType[]}) => { + const effectsRef = useRef(effects) + useEffect(() => { + effectsRef.current = effects + }, [effects]) + + useEffect(() => { + // todo use more precise timer for each effect + const interval = setInterval(() => { + for (const [index, effect] of effectsRef.current.entries()) { + if (effect.time === 0) { + // effect.removeEffect(effect.image) + return + } + effect.reduceTime(effect.image) + } + }, 1000) + + return () => { + clearInterval(interval) + } + }, []) + + const indicatorsMapped = Object.entries(defaultIndicatorsState).map(([key, state]) => ({ + icon: indicatorIcons[key], + // preserve order + state: indicators[key], + })) + return
+
+ { + indicatorsMapped.map((indicator) =>
+ +
) + } +
+
+ { + effects.map( + (effect) => + ) + } +
+
+} diff --git a/src/react/IndicatorEffectsProvider.tsx b/src/react/IndicatorEffectsProvider.tsx new file mode 100644 index 000000000..00b78d8a1 --- /dev/null +++ b/src/react/IndicatorEffectsProvider.tsx @@ -0,0 +1,118 @@ +import { proxy, useSnapshot } from 'valtio' +import { useEffect, useMemo } from 'react' +import { inGameError } from '../utils' +import { fsState } from '../loadSave' +import { miscUiState } from '../globalState' +import IndicatorEffects, { EffectType, defaultIndicatorsState } from './IndicatorEffects' +import { images } from './effectsImages' + +export const state = proxy({ + indicators: { + chunksLoading: false + }, + effects: [] as EffectType[] +}) + +export const addEffect = (newEffect: Omit) => { + const effectIndex = getEffectIndex(newEffect as EffectType) + if (typeof effectIndex === 'number') { + state.effects[effectIndex].time = newEffect.time + state.effects[effectIndex].level = newEffect.level + } else { + const effect = { ...newEffect, reduceTime, removeEffect } + state.effects.push(effect) + } +} + +const removeEffect = (image: string) => { + for (const [index, effect] of (state.effects).entries()) { + if (effect.image === image) { + state.effects.splice(index, 1) + } + } +} + +const reduceTime = (image: string) => { + for (const [index, effect] of (state.effects).entries()) { + if (effect.image === image) { + effect.time -= 1 + } + } +} + +const getEffectIndex = (newEffect: EffectType) => { + for (const [index, effect] of (state.effects).entries()) { + if (effect.image === newEffect.image) { + return index + } + } + return null +} + +export default () => { + const stateIndicators = useSnapshot(state.indicators) + const { hasErrors } = useSnapshot(miscUiState) + const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState) + const allIndicators: typeof defaultIndicatorsState = { + readonlyFiles: isReadonly, + writingFiles: openWriteOperations > 0, + readingFiles: openReadOperations > 0, + appHasErrors: hasErrors, + ...stateIndicators, + } + + useEffect(() => { + let alreadyWaiting = false + const listener = () => { + if (alreadyWaiting) return + state.indicators.chunksLoading = true + alreadyWaiting = true + void viewer.waitForChunksToRender().then(() => { + state.indicators.chunksLoading = false + alreadyWaiting = false + }) + } + viewer.world.renderUpdateEmitter.on('dirty', listener) + + return () => { + viewer.world.renderUpdateEmitter.off('dirty', listener) + } + }, []) + + const effects = useSnapshot(state.effects) + + useMemo(() => { + const effectsImages = Object.fromEntries(loadedData.effectsArray.map((effect) => { + const nameKebab = effect.name.replaceAll(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).slice(1) + return [effect.id, images[nameKebab]] + })) + bot.on('entityEffect', (entity, effect) => { + if (entity.id !== bot.entity.id) return + const image = effectsImages[effect.id] ?? null + if (!image) { + inGameError(`received unknown effect id ${effect.id}}`) + return + } + const newEffect = { + image, + time: effect.duration / 20, // duration received in ticks + level: effect.amplifier, + } + addEffect(newEffect) + }) + bot.on('entityEffectEnd', (entity, effect) => { + if (entity.id !== bot.entity.id) return + const image = effectsImages[effect.id] ?? null + if (!image) { + inGameError(`received unknown effect id ${effect.id}}}`) + return + } + removeEffect(image) + }) + }, []) + + return +} diff --git a/src/react/effectsImages.ts b/src/react/effectsImages.ts new file mode 100644 index 000000000..21421e0f6 --- /dev/null +++ b/src/react/effectsImages.ts @@ -0,0 +1,76 @@ +import absorption from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/absorption.png' +import glowing from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/glowing.png' +import instant_health from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/instant_health.png' +import nausea from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/nausea.png' +import slow_falling from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/slow_falling.png' +import weakness from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/weakness.png' +import bad_omen from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/bad_omen.png' +import haste from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/haste.png' +import invisibility from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/invisibility.png' +import night_vision from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/night_vision.png' +import slowness from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/slowness.png' +import wither from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/wither.png' +import blindness from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/blindness.png' +import health_boost from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/health_boost.png' +import jump_boost from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/jump_boost.png' +import poison from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/poison.png' +import speed from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/speed.png' +import conduit_power from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/conduit_power.png' +import hero_of_the_village from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/hero_of_the_village.png' +import levitation from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/levitation.png' +import regeneration from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/regeneration.png' +import strength from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/strength.png' +import dolphins_grace from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/dolphins_grace.png' +import hunger from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/hunger.png' +import luck from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/luck.png' +import resistance from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/resistance.png' +import unluck from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/unluck.png' +import fire_resistance from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/fire_resistance.png' +import instant_damage from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/instant_damage.png' +import mining_fatigue from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/mining_fatigue.png' +import saturation from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/saturation.png' +import water_breathing from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/water_breathing.png' +import darkness from 'minecraft-assets/minecraft-assets/data/1.20.2/mob_effect/darkness.png' + +interface Images { + [key: string]: string; +} + +// Export an object containing image URLs +export const images: Images = { + absorption, + glowing, + instant_health, + nausea, + slow_falling, + weakness, + bad_omen, + haste, + invisibility, + night_vision, + slowness, + wither, + blindness, + health_boost, + jump_boost, + poison, + speed, + conduit_power, + hero_of_the_village, + levitation, + regeneration, + strength, + dolphins_grace, + hunger, + luck, + resistance, + unluck, + bad_luck: unluck, + good_luck: luck, + fire_resistance, + instant_damage, + mining_fatigue, + saturation, + water_breathing, + darkness +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index aa1d74dd9..d56cb8056 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -16,6 +16,7 @@ import ChatProvider from './react/ChatProvider' import TitleProvider from './react/TitleProvider' import ScoreboardProvider from './react/ScoreboardProvider' import SignEditorProvider from './react/SignEditorProvider' +import IndicatorEffectsProvider from './react/IndicatorEffectsProvider' import SoundMuffler from './react/SoundMuffler' import TouchControls from './react/TouchControls' import widgets from './react/widgets' @@ -65,6 +66,7 @@ const InGameUi = () => { + diff --git a/src/utils.ts b/src/utils.ts index 61ceeeb80..39bf940e1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,6 +20,12 @@ export const toNumber = (val) => { return isNaN(num) ? undefined : num } +export const inGameError = (err) => { + console.error(err) + // todo report + miscUiState.hasErrors = true +} + export const pointerLock = { get hasPointerLock () { return document.pointerLockElement From 029a7175ab00a98ee36b26ee37d72e91210c9f5c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 28 Mar 2024 01:41:48 +0300 Subject: [PATCH 58/86] fix page loadeding indicator showing sometimes --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index aaa4989c1..3e7d951da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -864,7 +864,7 @@ downloadAndOpenFile().then((downloadAction) => { const initialLoader = document.querySelector('.initial-loader') as HTMLElement | null if (initialLoader) { initialLoader.style.opacity = '0' - window.pageLoaded = true } +window.pageLoaded = true void possiblyHandleStateVariable() From dc9f15e9038ae1c02e9cd6f78b946d2a7b3d7658 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 9 Apr 2024 03:51:11 +0300 Subject: [PATCH 59/86] naive transparent --- prismarine-viewer/examples/shared.ts | 1 + prismarine-viewer/examples/webglRendererWorker.ts | 4 ++++ prismarine-viewer/viewer/lib/models.ts | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts index 21fe635d9..2a43c467a 100644 --- a/prismarine-viewer/examples/shared.ts +++ b/prismarine-viewer/examples/shared.ts @@ -2,4 +2,5 @@ export type BlockType = { textureIndex: number[] textureName?: string tint?: [number, number, number] + isTransparent?: boolean } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 1319a41c7..9716feb2a 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -187,6 +187,10 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // [camera.position.x, camera.position.y, camera.position.z - 2, 'dirt'], // ] const blocks = allBlocks.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) + blocks.sort((a, b) => { + const getScore = (b: BlockType) => b.isTransparent ? 1 : 0 + return getScore(b[3]) - getScore(a[3]) + }) globalThis.allBlocksSize = allBlocks.length cubePositions = new Float32Array(blocks.length * 3) cubeTextureIndices = new Float32Array(blocks.length * 6); diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 1638cd234..a21c67f0f 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -531,7 +531,8 @@ export function getSectionGeometry (sx, sy, sz, world: World) { attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { textureIndex: textures, textureName, - tint + tint, + isTransparent: block.name.includes('glass') // todo } satisfies BlockType } From 484844b35d7ea26a02b92aace566d4e08adb43e1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 9 Apr 2024 14:41:21 +0300 Subject: [PATCH 60/86] add playground scripts! --- package.json | 7 +- pnpm-lock.yaml | 903 ++++++++++++++++++++++++++++++++- prismarine-viewer/package.json | 1 + 3 files changed, 903 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 3fe21a9d4..04cf44adb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,12 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build && node scripts/build.js moveStorybookFiles", "start-experiments": "vite --config experiments/vite.config.ts", - "watch-worker": "node prismarine-viewer/buildWorker.mjs -w" + "watch-worker": "node prismarine-viewer/buildWorker.mjs -w", + "watch-other-workers": "node buildWorkers.mjs -w", + "run-playground": "run-p watch-worker watch-other-workers playground-server watch-playground", + "run-all": "run-p start run-playground", + "playground-server": "live-server --port=9090 prismarine-viewer/public", + "watch-playground": "node prismarine-viewer/esbuild.mjs -w" }, "keywords": [ "prismarine", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb79e917f..81363a742 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -356,6 +356,9 @@ importers: lil-gui: specifier: ^0.18.2 version: 0.18.2 + live-server: + specifier: ^1.2.2 + version: 1.2.2 looks-same: specifier: ^8.2.3 version: 8.2.3 @@ -5949,6 +5952,15 @@ packages: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} dev: false + /anymatch@2.0.0: + resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} + dependencies: + micromatch: 3.1.10 + normalize-path: 2.1.1 + transitivePeerDependencies: + - supports-color + dev: false + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -5957,6 +5969,18 @@ packages: picomatch: 2.3.1 dev: true + /apache-crypt@1.2.6: + resolution: {integrity: sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==} + engines: {node: '>=8'} + dependencies: + unix-crypt-td-js: 1.1.4 + dev: false + + /apache-md5@1.1.8: + resolution: {integrity: sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==} + engines: {node: '>=8'} + dev: false + /app-root-dir@1.0.2: resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} dev: true @@ -6002,6 +6026,21 @@ packages: tslib: 2.6.2 dev: true + /arr-diff@4.0.0: + resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} + engines: {node: '>=0.10.0'} + dev: false + + /arr-flatten@1.1.0: + resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} + engines: {node: '>=0.10.0'} + dev: false + + /arr-union@3.1.0: + resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} + engines: {node: '>=0.10.0'} + dev: false + /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} dependencies: @@ -6035,6 +6074,11 @@ packages: engines: {node: '>=8'} dev: true + /array-unique@0.3.2: + resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} + engines: {node: '>=0.10.0'} + dev: false + /array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} @@ -6137,6 +6181,11 @@ packages: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} dev: true + /assign-symbols@1.0.0: + resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} + engines: {node: '>=0.10.0'} + dev: false + /ast-types@0.14.2: resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} engines: {node: '>=4'} @@ -6162,6 +6211,10 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + /async-each@1.0.6: + resolution: {integrity: sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==} + dev: false + /async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} @@ -6186,6 +6239,12 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + /atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + dev: false + /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -6295,18 +6354,38 @@ packages: engines: {node: ^4.5.0 || >= 5.9} dev: false + /base@0.11.2: + resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} + engines: {node: '>=0.10.0'} + dependencies: + cache-base: 1.0.1 + class-utils: 0.3.6 + component-emitter: 1.3.0 + define-property: 1.0.0 + isobject: 3.0.1 + mixin-deep: 1.3.2 + pascalcase: 0.1.1 + dev: false + /basic-auth@2.0.1: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} dependencies: safe-buffer: 5.1.2 - dev: true + + /batch@0.6.1: + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + dev: false /bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} dependencies: tweetnacl: 0.14.5 + /bcryptjs@2.4.3: + resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + dev: false + /better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} @@ -6319,6 +6398,11 @@ packages: engines: {node: '>=0.6'} dev: true + /binary-extensions@1.13.1: + resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} + engines: {node: '>=0.10.0'} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -6428,6 +6512,24 @@ packages: dependencies: balanced-match: 1.0.2 + /braces@2.3.2: + resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} + engines: {node: '>=0.10.0'} + dependencies: + arr-flatten: 1.1.0 + array-unique: 0.3.2 + extend-shallow: 2.0.1 + fill-range: 4.0.0 + isobject: 3.0.1 + repeat-element: 1.1.4 + snapdragon: 0.8.2 + snapdragon-node: 2.1.1 + split-string: 3.1.0 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -6618,6 +6720,21 @@ packages: dev: false optional: true + /cache-base@1.0.1: + resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} + engines: {node: '>=0.10.0'} + dependencies: + collection-visit: 1.0.0 + component-emitter: 1.3.0 + get-value: 2.0.6 + has-value: 1.0.0 + isobject: 3.0.1 + set-value: 2.0.1 + to-object-path: 0.3.0 + union-value: 1.0.1 + unset-value: 1.0.0 + dev: false + /cachedir@2.4.0: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} @@ -6781,6 +6898,27 @@ packages: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} + /chokidar@2.1.8: + resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} + deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies + dependencies: + anymatch: 2.0.0 + async-each: 1.0.6 + braces: 2.3.2 + glob-parent: 3.1.0 + inherits: 2.0.4 + is-binary-path: 1.0.1 + is-glob: 4.0.3 + normalize-path: 3.0.0 + path-is-absolute: 1.0.1 + readdirp: 2.2.1 + upath: 1.2.0 + optionalDependencies: + fsevents: 1.2.13 + transitivePeerDependencies: + - supports-color + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -6814,6 +6952,16 @@ packages: safe-buffer: 5.2.1 dev: true + /class-utils@0.3.6: + resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} + engines: {node: '>=0.10.0'} + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + dev: false + /classnames@2.3.2: resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} dev: false @@ -6896,6 +7044,14 @@ packages: engines: {node: '>=6'} dev: false + /collection-visit@1.0.0: + resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} + engines: {node: '>=0.10.0'} + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + dev: false + /color-convert@0.5.3: resolution: {integrity: sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==} dev: false @@ -7028,6 +7184,18 @@ packages: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} dev: true + /connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} dev: false @@ -7085,6 +7253,11 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + /copy-descriptor@0.1.1: + resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} + engines: {node: '>=0.10.0'} + dev: false + /copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} dependencies: @@ -7408,6 +7581,11 @@ packages: character-entities: 2.0.2 dev: false + /decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + dev: false + /decompress-response@4.2.1: resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} engines: {node: '>=8'} @@ -7487,6 +7665,28 @@ packages: has-property-descriptors: 1.0.0 object-keys: 1.1.1 + /define-property@0.2.5: + resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} + engines: {node: '>=0.10.0'} + dependencies: + is-descriptor: 0.1.7 + dev: false + + /define-property@1.0.0: + resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} + engines: {node: '>=0.10.0'} + dependencies: + is-descriptor: 1.0.3 + dev: false + + /define-property@2.0.2: + resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-descriptor: 1.0.3 + isobject: 3.0.1 + dev: false + /defu@6.1.2: resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} dev: true @@ -7513,6 +7713,11 @@ packages: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false + /depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: false + /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -7701,6 +7906,10 @@ packages: resolution: {integrity: sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==} dev: false + /duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: false + /duplexify@3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} dependencies: @@ -8632,6 +8841,18 @@ packages: promise: 5.0.0 dev: false + /event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + dev: false + /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -8697,6 +8918,21 @@ packages: engines: {node: '>=6'} dev: false + /expand-brackets@2.1.4: + resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} + engines: {node: '>=0.10.0'} + dependencies: + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + posix-character-classes: 0.1.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -8760,9 +8996,40 @@ packages: transitivePeerDependencies: - supports-color + /extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + dependencies: + is-extendable: 0.1.1 + dev: false + + /extend-shallow@3.0.2: + resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} + engines: {node: '>=0.10.0'} + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + dev: false + /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + /extglob@2.0.4: + resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} + engines: {node: '>=0.10.0'} + dependencies: + array-unique: 0.3.2 + define-property: 1.0.0 + expand-brackets: 2.1.4 + extend-shallow: 2.0.1 + fragment-cache: 0.2.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /extract-zip@1.7.0: resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} hasBin: true @@ -8831,6 +9098,13 @@ packages: reusify: 1.0.4 dev: true + /faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + dependencies: + websocket-driver: 0.7.4 + dev: false + /fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} dependencies: @@ -8889,6 +9163,16 @@ packages: resolution: {integrity: sha512-6RS9gDchbn+qWmtV2uSjo5vmKizgfCQeb5jKmqx8HyzA3MoLqqyQxN+QcjkGBJt7FjJ9qFce67Auyya5rRRbpw==} engines: {node: '>= 10.4.0'} + /fill-range@4.0.0: + resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + dev: false + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -8896,6 +9180,21 @@ packages: to-regex-range: 5.0.1 dev: true + /finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} @@ -9005,6 +9304,11 @@ packages: dependencies: is-callable: 1.2.7 + /for-in@1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} + engines: {node: '>=0.10.0'} + dev: false + /foreground-child@2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} @@ -9045,10 +9349,21 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + /fragment-cache@0.2.1: + resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} + engines: {node: '>=0.10.0'} + dependencies: + map-cache: 0.2.2 + dev: false + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + /from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + dev: false + /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -9096,6 +9411,18 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + /fsevents@1.2.13: + resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} + engines: {node: '>= 4.0'} + os: [darwin] + deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2 + requiresBuild: true + dependencies: + bindings: 1.5.0 + nan: 2.18.0 + dev: false + optional: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -9239,6 +9566,11 @@ packages: resolve-pkg-maps: 1.0.0 dev: false + /get-value@2.0.6: + resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} + engines: {node: '>=0.10.0'} + dev: false + /getos@3.2.1: resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} dependencies: @@ -9291,6 +9623,13 @@ packages: dev: false optional: true + /glob-parent@3.1.0: + resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==} + dependencies: + is-glob: 3.1.0 + path-dirname: 1.0.2 + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -9502,6 +9841,37 @@ packages: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: false + /has-value@0.3.1: + resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} + engines: {node: '>=0.10.0'} + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + dev: false + + /has-value@1.0.0: + resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} + engines: {node: '>=0.10.0'} + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + dev: false + + /has-values@0.1.4: + resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} + engines: {node: '>=0.10.0'} + dev: false + + /has-values@1.0.0: + resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + dev: false + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} @@ -9585,6 +9955,16 @@ packages: engines: {node: '>=8'} dev: true + /http-auth@3.1.3: + resolution: {integrity: sha512-Jbx0+ejo2IOx+cRUYAGS1z6RGc6JfYUNkysZM4u4Sfk1uLlGv814F7/PIjQQAuThLdAWxb74JMGd5J8zex1VQg==} + engines: {node: '>=4.6.1'} + dependencies: + apache-crypt: 1.2.6 + apache-md5: 1.1.8 + bcryptjs: 2.4.3 + uuid: 3.4.0 + dev: false + /http-browserify@1.7.0: resolution: {integrity: sha512-Irf/LJXmE3cBzU1eaR4+NEX6bmVLqt1wkmDiA7kBwH7zmb0D8kBAXsDmQ88hhj/qv9iEZKlyGx/hrMcFi8sOHw==} dependencies: @@ -9595,6 +9975,16 @@ packages: /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + /http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + dev: false + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -9605,6 +9995,10 @@ packages: statuses: 2.0.1 toidentifier: 1.0.1 + /http-parser-js@0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + dev: false + /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} @@ -9795,6 +10189,10 @@ packages: once: 1.4.0 wrappy: 1.0.2 + /inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + dev: false + /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -9849,6 +10247,13 @@ packages: engines: {node: '>=8'} dev: true + /is-accessor-descriptor@1.0.1: + resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==} + engines: {node: '>= 0.10'} + dependencies: + hasown: 2.0.1 + dev: false + /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -9890,6 +10295,13 @@ packages: dependencies: has-bigints: 1.0.2 + /is-binary-path@1.0.1: + resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} + engines: {node: '>=0.10.0'} + dependencies: + binary-extensions: 1.13.1 + dev: false + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -9904,6 +10316,10 @@ packages: call-bind: 1.0.2 has-tostringtag: 1.0.0 + /is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: false + /is-builtin-module@3.2.1: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} @@ -9932,6 +10348,13 @@ packages: hasown: 2.0.1 dev: true + /is-data-descriptor@1.0.1: + resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} + engines: {node: '>= 0.4'} + dependencies: + hasown: 2.0.1 + dev: false + /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} @@ -9942,16 +10365,43 @@ packages: resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} dev: true + /is-descriptor@0.1.7: + resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==} + engines: {node: '>= 0.4'} + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + dev: false + + /is-descriptor@1.0.3: + resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} + engines: {node: '>= 0.4'} + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + dev: false + /is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} hasBin: true dev: true + /is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + dev: false + + /is-extendable@1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + dependencies: + is-plain-object: 2.0.4 + dev: false + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true /is-finalizationregistry@1.0.2: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} @@ -9973,12 +10423,18 @@ packages: dependencies: has-tostringtag: 1.0.0 + /is-glob@3.1.0: + resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: false + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - dev: true /is-gzip@1.0.0: resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} @@ -10028,6 +10484,13 @@ packages: dependencies: has-tostringtag: 1.0.0 + /is-number@3.0.0: + resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -10062,7 +10525,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: isobject: 3.0.1 - dev: true /is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} @@ -10142,6 +10604,16 @@ packages: get-intrinsic: 1.2.4 dev: true + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: false + + /is-wsl@1.1.0: + resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} + engines: {node: '>=4'} + dev: false + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -10168,10 +10640,16 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /isobject@2.1.0: + resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} + engines: {node: '>=0.10.0'} + dependencies: + isarray: 1.0.0 + dev: false + /isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - dev: true /isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} @@ -10516,10 +10994,23 @@ packages: json-buffer: 3.0.1 dev: true + /kind-of@3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-buffer: 1.1.6 + dev: false + + /kind-of@4.0.0: + resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} + engines: {node: '>=0.10.0'} + dependencies: + is-buffer: 1.1.6 + dev: false + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - dev: true /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} @@ -10612,6 +11103,28 @@ packages: lit-element: 3.3.3 lit-html: 2.8.0 + /live-server@1.2.2: + resolution: {integrity: sha512-t28HXLjITRGoMSrCOv4eZ88viHaBVIjKjdI5PO92Vxlu+twbk6aE0t7dVIaz6ZWkjPilYFV6OSdMYl9ybN2B4w==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + chokidar: 2.1.8 + colors: 1.4.0 + connect: 3.7.0 + cors: 2.8.5 + event-stream: 3.3.4 + faye-websocket: 0.11.4 + http-auth: 3.1.3 + morgan: 1.10.0 + object-assign: 4.1.1 + opn: 6.0.0 + proxy-middleware: 0.15.0 + send: 0.18.0 + serve-index: 1.9.1 + transitivePeerDependencies: + - supports-color + dev: false + /load-bmfont@1.4.1: resolution: {integrity: sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==} dependencies: @@ -10864,6 +11377,11 @@ packages: tmpl: 1.0.5 dev: true + /map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + dev: false + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -10878,6 +11396,17 @@ packages: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} dev: true + /map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + dev: false + + /map-visit@1.0.0: + resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} + engines: {node: '>=0.10.0'} + dependencies: + object-visit: 1.0.1 + dev: false + /markdown-it@14.0.0: resolution: {integrity: sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==} hasBin: true @@ -11212,6 +11741,27 @@ packages: - supports-color dev: false + /micromatch@3.1.10: + resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} + engines: {node: '>=0.10.0'} + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + braces: 2.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + extglob: 2.0.4 + fragment-cache: 0.2.1 + kind-of: 6.0.3 + nanomatch: 1.2.13 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -11433,6 +11983,14 @@ packages: minipass: 3.3.6 yallist: 4.0.0 + /mixin-deep@1.3.2: + resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} + engines: {node: '>=0.10.0'} + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + dev: false + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -11479,6 +12037,19 @@ packages: /moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + /morgan@1.10.0: + resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} + engines: {node: '>= 0.8.0'} + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -11534,6 +12105,25 @@ packages: hasBin: true dev: true + /nanomatch@1.2.13: + resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} + engines: {node: '>=0.10.0'} + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + fragment-cache: 0.2.1 + is-windows: 1.0.2 + kind-of: 6.0.3 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} requiresBuild: true @@ -11743,10 +12333,16 @@ packages: validate-npm-package-license: 3.0.4 dev: true + /normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + dependencies: + remove-trailing-separator: 1.1.0 + dev: false + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true /npm-run-all@4.1.5: resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} @@ -11801,6 +12397,15 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + /object-copy@0.1.0: + resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} + engines: {node: '>=0.10.0'} + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + dev: false + /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -11818,6 +12423,13 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + /object-visit@1.0.1: + resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: false + /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} engines: {node: '>= 0.4'} @@ -11862,6 +12474,13 @@ packages: es-abstract: 1.22.4 dev: true + /object.pick@1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: false + /object.values@1.1.7: resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} engines: {node: '>= 0.4'} @@ -11875,6 +12494,13 @@ packages: resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} dev: false + /on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -11910,6 +12536,14 @@ packages: hasBin: true dev: true + /opn@6.0.0: + resolution: {integrity: sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==} + engines: {node: '>=8'} + deprecated: The package has been renamed to `open` + dependencies: + is-wsl: 1.1.0 + dev: false + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -12092,6 +12726,11 @@ packages: tslib: 2.6.2 dev: false + /pascalcase@0.1.1: + resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} + engines: {node: '>=0.10.0'} + dev: false + /path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} dev: true @@ -12103,6 +12742,10 @@ packages: tslib: 2.6.2 dev: false + /path-dirname@1.0.2: + resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} + dev: false + /path-exists-cli@2.0.0: resolution: {integrity: sha512-qGr0A87KYCznmvabblxyxnzA/MtPZ28wH+4SCMP4tjTFAbzqwvs5xpUZExAYzq5OgHe5vIswzdH5iosCb8YF/Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -12173,6 +12816,12 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true + /pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + dependencies: + through: 2.3.8 + dev: false + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -12318,6 +12967,11 @@ packages: - supports-color dev: true + /posix-character-classes@0.1.1: + resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} + engines: {node: '>=0.10.0'} + dev: false + /postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -12717,6 +13371,11 @@ packages: /proxy-from-env@1.0.0: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} + /proxy-middleware@0.15.0: + resolution: {integrity: sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==} + engines: {node: '>=0.8.0'} + dev: false + /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -13225,6 +13884,17 @@ packages: process: 0.11.10 string_decoder: 1.3.0 + /readdirp@2.2.1: + resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} + engines: {node: '>=0.10'} + dependencies: + graceful-fs: 4.2.11 + micromatch: 3.1.10 + readable-stream: 2.3.8 + transitivePeerDependencies: + - supports-color + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -13299,6 +13969,14 @@ packages: dependencies: '@babel/runtime': 7.22.11 + /regex-not@1.0.2: + resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + dev: false + /regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true @@ -13399,6 +14077,20 @@ packages: - supports-color dev: false + /remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + dev: false + + /repeat-element@1.1.4: + resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} + engines: {node: '>=0.10.0'} + dev: false + + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: false + /request-progress@3.0.0: resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} dependencies: @@ -13433,6 +14125,11 @@ packages: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} dev: false + /resolve-url@0.2.1: + resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} + deprecated: https://github.com/lydell/resolve-url#deprecated + dev: false + /resolve@1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true @@ -13600,6 +14297,12 @@ packages: is-regex: 1.1.4 dev: true + /safe-regex@1.1.0: + resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} + dependencies: + ret: 0.1.15 + dev: false + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -13692,6 +14395,21 @@ packages: randombytes: 2.1.0 dev: false + /serve-index@1.9.1: + resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + engines: {node: '>= 0.8.0'} + dependencies: + accepts: 1.3.8 + batch: 0.6.1 + debug: 2.6.9 + escape-html: 1.0.3 + http-errors: 1.6.3 + mime-types: 2.1.35 + parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color + dev: false + /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} @@ -13731,9 +14449,23 @@ packages: engines: {node: '>=6.9'} dev: false + /set-value@2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + dev: false + /setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + /setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + dev: false + /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -13902,6 +14634,38 @@ packages: tslib: 2.6.2 dev: false + /snapdragon-node@2.1.1: + resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} + engines: {node: '>=0.10.0'} + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + dev: false + + /snapdragon-util@3.0.1: + resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /snapdragon@0.8.2: + resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} + engines: {node: '>=0.10.0'} + dependencies: + base: 0.11.2 + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + map-cache: 0.2.2 + source-map: 0.5.7 + source-map-resolve: 0.5.3 + use: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: false + /socket.io-adapter@1.1.2: resolution: {integrity: sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==} dev: false @@ -14039,12 +14803,28 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-resolve@0.5.3: + resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + resolve-url: 0.2.1 + source-map-url: 0.4.1 + urix: 0.1.0 + dev: false + /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + /source-map-url@0.4.1: + resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} + deprecated: See https://github.com/lydell/source-map-url#deprecated + dev: false + /source-map@0.5.6: resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} engines: {node: '>=0.10.0'} @@ -14097,6 +14877,19 @@ packages: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true + /split-string@3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 3.0.2 + dev: false + + /split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + dependencies: + through: 2.3.8 + dev: false + /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -14154,6 +14947,14 @@ packages: stacktrace-gps: 3.1.2 dev: false + /static-extend@0.1.2: + resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} + engines: {node: '>=0.10.0'} + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + dev: false + /stats-gl@1.0.5: resolution: {integrity: sha512-XimMxvwnf1Qf5KwebhcoA34kcX+fWEkIl0QjNkCbu4IpoyDMMsOajExn7FIq5w569k45+LhmsuRlGSrsvmGdNw==} dev: false @@ -14162,6 +14963,11 @@ packages: resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==} dev: false + /statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + dev: false + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -14193,6 +14999,12 @@ packages: readable-stream: 3.6.2 dev: true + /stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + dependencies: + duplexer: 0.1.2 + dev: false + /stream-shift@1.0.1: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} dev: true @@ -14593,6 +15405,21 @@ packages: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + /to-object-path@0.3.0: + resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} + engines: {node: '>=0.10.0'} + dependencies: + kind-of: 3.2.2 + dev: false + + /to-regex-range@2.1.1: + resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} + engines: {node: '>=0.10.0'} + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + dev: false + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -14600,6 +15427,16 @@ packages: is-number: 7.0.0 dev: true + /to-regex@3.0.2: + resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} + engines: {node: '>=0.10.0'} + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + dev: false + /tocbot@4.21.2: resolution: {integrity: sha512-R5Muhi/TUu4i4snWVrMgNoXyJm2f8sJfdgIkQvqb+cuIXQEIMAiWGWgCgYXHqX4+XiS/Bnm7IYZ9Zy6NVe6lhw==} dev: true @@ -14893,6 +15730,16 @@ packages: vfile: 6.0.1 dev: false + /union-value@1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + dev: false + /union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} engines: {node: '>= 0.8.0'} @@ -14983,6 +15830,10 @@ packages: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} + /unix-crypt-td-js@1.1.4: + resolution: {integrity: sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==} + dev: false + /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -14996,6 +15847,14 @@ packages: webpack-virtual-modules: 0.5.0 dev: true + /unset-value@1.0.0: + resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} + engines: {node: '>=0.10.0'} + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + dev: false + /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -15032,6 +15891,11 @@ packages: dependencies: punycode: 2.3.0 + /urix@0.1.0: + resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} + deprecated: Please see https://github.com/lydell/urix#deprecated + dev: false + /url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} dev: true @@ -15116,6 +15980,11 @@ packages: use-deep-compare: 1.1.0(react@18.2.0) dev: true + /use@3.1.1: + resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} + engines: {node: '>=0.10.0'} + dev: false + /utf8-byte-length@1.0.4: resolution: {integrity: sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==} dev: false @@ -15147,6 +16016,12 @@ packages: dependencies: macaddress: 0.5.3 + /uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: false + /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -15429,6 +16304,20 @@ packages: sdp: 3.2.0 dev: false + /websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + dependencies: + http-parser-js: 0.5.8 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + dev: false + + /websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + dev: false + /whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} diff --git a/prismarine-viewer/package.json b/prismarine-viewer/package.json index 529aa9ae4..6942bb876 100644 --- a/prismarine-viewer/package.json +++ b/prismarine-viewer/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@tweenjs/tween.js": "^20.0.3", + "live-server": "^1.2.2", "assert": "^2.0.0", "buffer": "^6.0.3", "canvas": "^2.11.2", From c5c8f27e1832e2d97337c5ff100cee3a5d1ca63e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 9 Apr 2024 16:29:44 +0300 Subject: [PATCH 61/86] allow to override --- prismarine-viewer/examples/webglRenderer.ts | 3 +- .../examples/webglRendererWorker.ts | 38 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index c6b5b2afc..8fc08a19d 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -93,7 +93,8 @@ export const initWebglRenderer = async (version: string, postRender = () => { }, sendWorkerMessage({ canvas: offscreen, imageBlob, - isPlayground + isPlayground, + FragShaderOverride: localStorage.FragShader }, [offscreen]) let oldWidth = window.innerWidth diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 9716feb2a..25ba9cc45 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -38,7 +38,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im const gl = canvas.getContext('webgl2')! - const program = createProgram(gl, VertShader, FragShader) + const program = createProgram(gl, VertShader, FragShaderOverride || FragShader) let vertices = new Float32Array([ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0, // Bottom-let @@ -264,6 +264,13 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindBuffer(gl.ARRAY_BUFFER, null); } + globalThis.fullReset = () => { + allBlocks = [] + globalThis.cleanupFirstChunks() + lastNotUpdatedIndex = undefined + lastNotUpdatedArrSize = undefined + } + //gl.bindBuffer(gl.ARRAY_BUFFER, null); @@ -339,7 +346,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im } gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) - gl.clearColor( 0.6784313725490196, 0.8470588235294118, 0.9019607843137255 , 0.0); + gl.clearColor(0.6784313725490196, 0.8470588235294118, 0.9019607843137255, 0.0); gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.DEPTH_BUFFER_BIT) @@ -405,7 +412,7 @@ let autoTickUpdate = undefined as number | undefined onmessage = function (e) { if (!started) { started = true - initWebglRenderer(e.data.canvas, e.data.imageBlob, e.data.isPlayground) + initWebglRenderer(e.data.canvas, e.data.imageBlob, e.data.isPlayground, e.data.FragShaderOverride) return } if (e.data.type === 'startRender') { @@ -420,7 +427,6 @@ onmessage = function (e) { } if (e.data.type === 'addBlocksSection') { const currentLength = allBlocks.length; - chunksArrIndexes[e.data.key] = [currentLength, currentLength + e.data.data.length] // in: object - name, out: [x, y, z, name] const newData = Object.entries(e.data.data.blocks).map(([key, value]) => { const [x, y, z] = key.split(',').map(Number) @@ -428,19 +434,7 @@ onmessage = function (e) { }) // find freeIndexes if possible const freeArea = freeArrayIndexes.find(([startIndex, endIndex]) => endIndex - startIndex >= newData.length) - // if (freeArea) { - // const [startIndex, endIndex] = freeArea - // allBlocks.splice(startIndex, newData.length, ...newData) - // lastNotUpdatedIndex ??= startIndex - // const freeAreaIndex = freeArrayIndexes.indexOf(freeArea) - // freeArrayIndexes[freeAreaIndex] = [startIndex + newData.length, endIndex] - // if (freeArrayIndexes[freeAreaIndex][0] >= freeArrayIndexes[freeAreaIndex][1]) { - // freeArrayIndexes.splice(freeAreaIndex, 1) - // // todo merge - // } - // lastNotUpdatedArrSize = newData.length - // console.log('using free area', freeArea) - // } + chunksArrIndexes[e.data.key] = [currentLength, currentLength + newData.length] allBlocks.push(...newData) lastNotUpdatedIndex ??= currentLength // updateCubes?.(currentLength) @@ -455,11 +449,11 @@ onmessage = function (e) { freeArrayIndexes.push([startIndex, endIndex]) // merge freeArrayIndexes TODO - // if (freeArrayIndexes.at(-1)[0] === freeArrayIndexes.at(-2)[1]) { - // const [startIndex, endIndex] = freeArrayIndexes.pop()! - // const [startIndex2, endIndex2] = freeArrayIndexes.pop()! - // freeArrayIndexes.push([startIndex2, endIndex]) - // } + if (freeArrayIndexes.at(-1)[0] === freeArrayIndexes.at(-2)?.[1]) { + const [startIndex, endIndex] = freeArrayIndexes.pop()! + const [startIndex2, endIndex2] = freeArrayIndexes.pop()! + freeArrayIndexes.push([startIndex2, endIndex]) + } } if (e.data.type === 'camera') { camera.rotation.set(e.data.camera.rotation.x, e.data.camera.rotation.y, e.data.camera.rotation.z, 'ZYX') From 28d55f9cfc06c9147e2ca0e7fae6625787cc57e5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 9 Apr 2024 16:33:06 +0300 Subject: [PATCH 62/86] reload on worker change --- scripts/esbuildPlugins.mjs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index 95d88b8ed..4f9b0ddd4 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -41,6 +41,11 @@ const plugins = [ return { contents: `window.mcData ??= ${JSON.stringify(defaultVersionsObj)};module.exports = { pc: window.mcData }`, loader: 'js', + watchFiles: [ + // todo + 'dist/worker.js', + 'dist/webglRendererWorker.js' + ], } }) build.onResolve({ @@ -148,8 +153,9 @@ const plugins = [ //@ts-ignore const outputFile = outputFiles.find(x => x.path.endsWith('.js')) if (outputFile.hash === prevHash) { - console.log('Ignoring reload as contents the same') - return + // todo also check workers and css + // console.log('Ignoring reload as contents the same') + // return } prevHash = outputFile.hash let outputText = outputFile.text From 0edf51aef397d43a6a0dfb8045ed666ddc3f8935 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 9 Apr 2024 16:43:34 +0300 Subject: [PATCH 63/86] better workarounds! --- prismarine-viewer/examples/webglRendererWorker.ts | 3 ++- prismarine-viewer/viewer/lib/models.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 25ba9cc45..25f5bbe25 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -30,7 +30,8 @@ const updateSize = (width, height) => { camera.updateProjectionMatrix() } -export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: ImageBitmapSource, isPlayground: boolean) => { + +export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: ImageBitmapSource, isPlayground: boolean, FragShaderOverride?) => { // blockStates = blockStatesJson const textureBitmap = await createImageBitmap(imageBlob) const textureWidth = textureBitmap.width diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index a21c67f0f..853c0414d 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -532,7 +532,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { textureIndex: textures, textureName, tint, - isTransparent: block.name.includes('glass') // todo + isTransparent: block.name.includes('glass') || block.name.includes('grass') || block.name.includes('leaves') || block.name.includes('fire') // todo } satisfies BlockType } From 469ef4982e0e0cc0c3ddbd0177b7c6a26e517015 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 9 Apr 2024 22:41:28 +0300 Subject: [PATCH 64/86] [pick] allow to set 0 render distance for debug --- src/utils.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 39bf940e1..4b1239d5e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -161,10 +161,18 @@ let prevRenderDistance = options.renderDistance export const setRenderDistance = () => { assertDefined(worldView) const { renderDistance: singleplayerRenderDistance, multiplayerRenderDistance } = options - const renderDistance = miscUiState.singleplayer ? singleplayerRenderDistance : multiplayerRenderDistance + let renderDistance = miscUiState.singleplayer ? singleplayerRenderDistance : multiplayerRenderDistance + const zeroRenderDistance = miscUiState.singleplayer && renderDistance === 0 + if (zeroRenderDistance) { + renderDistance = 1 // mineflayer limitation workaround + } bot.setSettings({ viewDistance: renderDistance }) + if (zeroRenderDistance) { + localServer!.players[0].view = 0 + renderDistance = 0 + } worldView.viewDistance = renderDistance prevRenderDistance = renderDistance } From ed74485b01ce7af24706d97f6d05c380f10ed855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Wed, 10 Apr 2024 00:58:46 +0300 Subject: [PATCH 65/86] Quad instanced rendreing --- prismarine-viewer/examples/_VertexShader.vert | 56 ++- prismarine-viewer/examples/shared.ts | 9 +- .../examples/webglRendererWorker.ts | 254 ++++++----- prismarine-viewer/viewer/lib/models.ts | 400 ++++++++++++------ .../viewer/lib/worldrendererCommon.ts | 1 + src/index.ts | 1 + 6 files changed, 471 insertions(+), 250 deletions(-) diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index f1f6b0748..9410c1c67 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -4,11 +4,10 @@ layout (location = 1) in vec2 aTexCoord; layout (location = 2) in float CubeSide; layout (location = 3) in vec3 aOffset; -layout (location = 4) in vec4 aTextureIndex; -layout (location = 5) in vec2 aTextureIndexPlus; +layout (location = 4) in float aTextureIndex; layout (location = 6) in vec3 aBiomeColor; - +//#Define out vec2 TexCoord; flat out float TextureIndex; @@ -18,38 +17,67 @@ uniform mat4 view; uniform mat4 projection; uniform int tick; +mat4 rotationX( in float angle ) { + return mat4( 1.0, 0, 0, 0, + 0, cos(angle), -sin(angle), 0, + 0, sin(angle), cos(angle), 0, + 0, 0, 0, 1); +} + +mat4 rotationY( in float angle ) { + return mat4( cos(angle), 0, sin(angle), 0, + 0, 1.0, 0, 0, + -sin(angle), 0, cos(angle), 0, + 0, 0, 0, 1); +} + void main() { - gl_Position = projection * view * vec4(aPos + aOffset + vec3(0.5f,0.5f,0.5f), 1.0f); //Offseting by 0.5 to center the cube + //vec3 TransitionedPos = aPos; + vec3 TransitionedPos ;//= (vec4(aPos,0.0f) *rotationX(radians(180.0f))).xyz; + TexCoord = vec2(aTexCoord.x, (1.0 - aTexCoord.y)); // Flipping image for opengl coordinates - //TextureIndex = aTextureIndex; //Passing texture index to fragment shader + TextureIndex = aTextureIndex; //Passing texture index to fragment shader switch (int(CubeSide)) { case 0: TexCoord = vec2((1.0f-aTexCoord.x), (1.0 - aTexCoord.y)); - TextureIndex = aTextureIndex.x; + //TextureIndex = aTextureIndex.x; + //TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; + + TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(-90.0f))).xyz; break; case 1: - TextureIndex = aTextureIndex.y; + //TextureIndex = aTextureIndex.y; + TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(90.0f))).xyz; break; case 2: - TextureIndex = aTextureIndex.z; - TexCoord = vec2((1.0f-aTexCoord.y), (1.0f - aTexCoord.x)); + //TextureIndex = aTextureIndex.z; + //TexCoord = vec2((1.0f-aTexCoord.y), (1.0f - aTexCoord.x)); + TransitionedPos = vec4(aPos,0.0f).xyz; break; case 3: - TextureIndex = aTextureIndex.z; - TexCoord = vec2(aTexCoord.y, (1.0f - aTexCoord.x)); + //TextureIndex = aTextureIndex.z; + //TexCoord = vec2(aTexCoord.y, (1.0f - aTexCoord.x)); + + //TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(-90.0f))).xyz; + TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; break; case 4: - TextureIndex = aTextureIndexPlus.x; + //TextureIndex = aTextureIndexPlus.x; + //TransitionedPos = vec4(aPos,0.0f).xyz; + TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(-90.0f))).xyz; break; case 5: - TextureIndex = aTextureIndexPlus.y; + //TextureIndex = aTextureIndexPlus.y; + //TransitionedPos = vec4(aPos,0.0f).xyz; + TexCoord = vec2(aTexCoord); + TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(180.0f))).xyz; break; } TextureIndex += float(tick); BiomeColor = aBiomeColor; - + gl_Position = projection * view * vec4(TransitionedPos + aOffset + vec3(0.5f,0.5f,0.5f), 1.0f); //Offseting by 0.5 to center the cube //CubeSideIndex = CubeSide; //Passing cube side index to fragment shader } diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts index 2a43c467a..318396fae 100644 --- a/prismarine-viewer/examples/shared.ts +++ b/prismarine-viewer/examples/shared.ts @@ -1,6 +1,11 @@ -export type BlockType = { - textureIndex: number[] +export type BlockFaceType = { + face: number + textureIndex: number textureName?: string tint?: [number, number, number] isTransparent?: boolean } + +export type BlockType = { + sides: BlockFaceType[] +} diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 25f5bbe25..b82c2329c 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -4,13 +4,13 @@ import * as THREE from 'three' import VertShader from './_VertexShader.vert' //@ts-ignore import FragShader from './_FragmentShader.frag' -import { BlockType } from './shared' +import { BlockFaceType, BlockType } from './shared' -let allBlocks = [] +let allSides = [] as [number, number, number, BlockFaceType][] let chunksArrIndexes = {} -let freeArrayIndexes = [] +let freeArrayIndexes = [] as [number, number][] let rendering = true -let cubePositions +let sidePositions let updateCubes: (startIndex: any) => void let lastNotUpdatedIndex let lastNotUpdatedArrSize @@ -41,7 +41,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im const program = createProgram(gl, VertShader, FragShaderOverride || FragShader) - let vertices = new Float32Array([ + let CubeMesh = new Float32Array([ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0, // Bottom-let 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, // bottom-right 0.5, 0.5, -0.5, 1.0, 1.0, 0.0, // top-right @@ -85,57 +85,106 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im -0.5, 0.5, -0.5, 0.0, 1.0, 5.0// top-let ]) + let SideMesh = new Float32Array([ + -0.5, -0.5, -0.5, 0.0, 0.0, // Bottom-let + 0.5, -0.5, -0.5, 1.0, 0.0, // bottom-right + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + -0.5, 0.5, -0.5, 0.0, 1.0, // top-let + -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let + // ront ace + ]) + + + let NumberOfCube = isPlayground ? 1_000_000 : 5_000_000 - cubePositions = new Float32Array(NumberOfCube * 3) - let cubeTextureIndices = new Float32Array(NumberOfCube * 6); - let cubeBiomeColor = new Float32Array(NumberOfCube * 3); + sidePositions = new Float32Array(NumberOfCube * 3 * 6) + let sideTextureIndices = new Float32Array(NumberOfCube * 1 * 6); + let sideIndexes = new Float32Array(NumberOfCube * 1 * 6); + let sideBiomeColor = new Float32Array(NumberOfCube * 3 * 6); // write random coordinates to cube positions xyz ten cubes; - if (isPlayground) { - for (let i = 0; i < NumberOfCube * 3; i += 3) { - cubePositions[i] = Math.floor(Math.random() * 1000) - 500; - cubePositions[i + 1] = Math.floor(Math.random() * 1000) - 500; - cubePositions[i + 2] = Math.floor(Math.random() * 100) - 100; - cubeBiomeColor[i] = (Math.random() ) ; - cubeBiomeColor[i + 1] = (Math.random() ) ; - cubeBiomeColor[i + 2] = (Math.random() ) ; + if (false) { + for (let i = 0; i < NumberOfCube * 18; i += 18) { + + sidePositions[i] = Math.floor(Math.random() * 1000) - 500; + sidePositions[i + 1] = Math.floor(Math.random() * 1000) - 500; + sidePositions[i + 2] = Math.floor(Math.random() * 100) - 100; + + sideBiomeColor[i] = (Math.random()); + sideBiomeColor[i + 1] = (Math.random()); + sideBiomeColor[i + 2] = (Math.random()); + for (let j = 1; j <= 6; j++) { + + if (j != 6) { + sidePositions[j * 3 + i] = sidePositions[i] + sidePositions[j * 3 + i + 1] = sidePositions[i + 1] + sidePositions[j * 3 + i + 2] = sidePositions[i + 2] + + sideBiomeColor[j * 3 + i] = sideBiomeColor[i] + sideBiomeColor[j * 3 + i + 1] = sideBiomeColor[i + 1] + sideBiomeColor[j * 3 + i + 2] = sideBiomeColor[i + 2] + } + + sideIndexes[i / 3 + j - 1] = j - 1; + //sideTextureIndices[i / 3 + j - 1] = Math.floor(Math.random() * 800); + sideTextureIndices[i / 3 + j - 1] = 1; + } + + // sidePositions[i +3] = sidePositions[i] + // sidePositions[i + 4] = sidePositions[i + 2] + // sidePositions[i + 5] = sidePositions[i + 1] + + // sideBiomeColor[i] = (Math.random() ) ; + // sideBiomeColor[i + 1] = (Math.random() ) ; + // sideBiomeColor[i + 2] = (Math.random() ) ; + + // sideIndexes[i/6] = Math.floor(Math.random() * 6); + // sideTextureIndices[i/6] = Math.floor(Math.random() * 800); // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); // cubeTextureIndices[i / 3] = 0; } - for (let i = 0; i < NumberOfCube * 6; i += 6) { - cubeTextureIndices[i + 0] = Math.floor(Math.random() * 800); - cubeTextureIndices[i + 1] = Math.floor(Math.random() * 800); - cubeTextureIndices[i + 2] = Math.floor(Math.random() * 800); - cubeTextureIndices[i + 3] = Math.floor(Math.random() * 800); - cubeTextureIndices[i + 4] = Math.floor(Math.random() * 800); - cubeTextureIndices[i + 5] = Math.floor(Math.random() * 800); - // cubeTextureIndices[i / 3] = 0; - } + // for (let i = 0; i < NumberOfCube * 6; i += 6) { + // sideTextureIndices[i + 0] = Math.floor(Math.random() * 800); + // sideTextureIndices[i + 1] = Math.floor(Math.random() * 800); + // sideTextureIndices[i + 2] = Math.floor(Math.random() * 800); + // sideTextureIndices[i + 3] = Math.floor(Math.random() * 800); + // sideTextureIndices[i + 4] = Math.floor(Math.random() * 800); + // sideTextureIndices[i + 5] = Math.floor(Math.random() * 800); + // // cubeTextureIndices[i / 3] = 0; + // } } - cubePositions[0] = 0; - cubePositions[1] = 0; - cubePositions[2] = 0; + // cubePositions[0] = 0; + // cubePositions[1] = 0; + // cubePositions[2] = 0; let VAO = gl.createVertexArray(); + + let instanceVBO = gl.createBuffer(); let instanceTextureID = gl.createBuffer(); let instanceBiomeColor = gl.createBuffer(); + let instanceCubeSide = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.bufferData(gl.ARRAY_BUFFER, cubePositions, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sidePositions, gl.DYNAMIC_DRAW); // todo gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.bufferData(gl.ARRAY_BUFFER, cubeTextureIndices, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sideTextureIndices, gl.DYNAMIC_DRAW); // todo gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); - gl.bufferData(gl.ARRAY_BUFFER, cubeBiomeColor, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sideBiomeColor, gl.DYNAMIC_DRAW); // todo + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); + gl.bufferData(gl.ARRAY_BUFFER, sideIndexes, gl.DYNAMIC_DRAW); // todo gl.bindBuffer(gl.ARRAY_BUFFER, null); VAO = gl.createVertexArray(); let VBO = gl.createBuffer(); @@ -145,16 +194,16 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindVertexArray(VAO); gl.bindBuffer(gl.ARRAY_BUFFER, VBO) // gl.bindBuffer(gl.ARRAY_BUFFER, VBO_sides) - gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) + gl.bufferData(gl.ARRAY_BUFFER, SideMesh, gl.STATIC_DRAW) - gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 6 * 4, 0) + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 5 * 4, 0) gl.enableVertexAttribArray(0) - gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 6 * 4, 3 * 4) + gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) gl.enableVertexAttribArray(1) - gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 6 * 4, 5 * 4) - gl.enableVertexAttribArray(2) + //gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 6 * 4, 5 * 4) + // gl.enableVertexAttribArray(2) //instance data gl.enableVertexAttribArray(3); @@ -164,13 +213,19 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.vertexAttribDivisor(3, 1); gl.enableVertexAttribArray(4); - gl.enableVertexAttribArray(5); + // gl.enableVertexAttribArray(5); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.vertexAttribPointer(4, 4, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(5, 2, gl.FLOAT, false, 4 * 6, 4 * 4); + gl.vertexAttribPointer(4, 1, gl.FLOAT, false, 4 * 1, 0); + // gl.vertexAttribPointer(5, 2, gl.FLOAT, false, 4 * 6, 4 * 4); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(4, 1); - gl.vertexAttribDivisor(5, 1); + // gl.vertexAttribDivisor(5, 1); + + gl.enableVertexAttribArray(6); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); + gl.vertexAttribPointer(6, 3, gl.FLOAT, false, 3 * 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(6, 1); gl.enableVertexAttribArray(6); gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); @@ -178,55 +233,46 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(6, 1); + gl.enableVertexAttribArray(2); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); + gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(2, 1); + updateCubes = (startIndex) => { - // cubePositionsRaw = [ - // // for now one cube in front of the camera - // [camera.position.x, camera.position.y, camera.position.z, 'dirt'], - // [camera.position.x + 2, camera.position.y, camera.position.z, 'dirt'], - // [camera.position.x - 2, camera.position.y, camera.position.z, 'dirt'], - // [camera.position.x, camera.position.y, camera.position.z + 2, 'dirt'], - // [camera.position.x, camera.position.y, camera.position.z - 2, 'dirt'], - // ] - const blocks = allBlocks.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) - blocks.sort((a, b) => { - const getScore = (b: BlockType) => b.isTransparent ? 1 : 0 - return getScore(b[3]) - getScore(a[3]) + // up2 + const newSides = allSides.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) + newSides.sort((a, b) => { + const getScore = (b: BlockFaceType) => b.isTransparent ? 0 : 1 + return getScore(a[3]) - getScore(b[3]) }) - globalThis.allBlocksSize = allBlocks.length - cubePositions = new Float32Array(blocks.length * 3) - cubeTextureIndices = new Float32Array(blocks.length * 6); - cubeBiomeColor = new Float32Array(blocks.length * 3); - for (let i = 0; i < blocks.length * 3; i += 3) { - cubePositions[i] = blocks[i / 3][0] - cubePositions[i + 1] = blocks[i / 3][1] - cubePositions[i + 2] = blocks[i / 3][2] - const block = blocks[i / 3][3] as BlockType + globalThis.allSidesSize = allSides.length + sidePositions = new Float32Array(newSides.length * 3) + sideTextureIndices = new Float32Array(newSides.length * 1); + sideBiomeColor = new Float32Array(newSides.length * 3); + for (let i = 0; i < newSides.length * 3; i += 3) { + sidePositions[i] = newSides[i / 3][0] + sidePositions[i + 1] = newSides[i / 3][1] + sidePositions[i + 2] = newSides[i / 3][2] + const block = newSides[i / 3][3] as BlockFaceType if (block.tint) { const [r, g, b] = block.tint - cubeBiomeColor[i] = r - cubeBiomeColor[i + 1] = g - cubeBiomeColor[i + 2] = b + sideBiomeColor[i] = r + sideBiomeColor[i + 1] = g + sideBiomeColor[i + 2] = b } else { - cubeBiomeColor[i] = 1 - cubeBiomeColor[i + 1] = 1 - cubeBiomeColor[i + 2] = 1 + sideBiomeColor[i] = 1 + sideBiomeColor[i + 1] = 1 + sideBiomeColor[i + 2] = 1 } - } - - for (let i = 0; i < blocks.length * 6; i += 6) { - const block = blocks[i / 6][3] as BlockType - cubeTextureIndices[i + 0] = block.textureIndex[0] - cubeTextureIndices[i + 1] = block.textureIndex[1] - cubeTextureIndices[i + 2] = block.textureIndex[2] - cubeTextureIndices[i + 3] = block.textureIndex[3] - cubeTextureIndices[i + 4] = block.textureIndex[4] - cubeTextureIndices[i + 5] = block.textureIndex[5] + sideTextureIndices[i / 3] = block.textureIndex + sideIndexes[i / 3] = block.face } // startIndex = 0 // TODO! - console.log('startIndex', startIndex, cubePositions.length, allBlocks.length) - const updateBuffersSize = allBlocks.length > NumberOfCube + console.log('startIndex', startIndex, sidePositions.length, allSides.length) + const updateBuffersSize = allSides.length > NumberOfCube if (updateBuffersSize) { NumberOfCube += 1_000_000 } @@ -234,26 +280,29 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im if (updateBuffersSize) { //gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NumberOfCube * 3), gl.STATIC_DRAW); } - const POS_SIZE = 3 - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * POS_SIZE, cubePositions); // update buffer content + const XYZ_SIZE = 3 + gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * XYZ_SIZE, sidePositions); // update buffer content gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); if (updateBuffersSize) { //gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NumberOfCube), gl.STATIC_DRAW); } - const TEXTURES_SIZE = 6 - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * TEXTURES_SIZE, cubeTextureIndices); // update buffer content + gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4, sideTextureIndices); // update buffer content gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * POS_SIZE, cubeBiomeColor); // update buffer content + gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * XYZ_SIZE, sideBiomeColor); // update buffer content + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); + gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4, sideIndexes); // update buffer content gl.bindBuffer(gl.ARRAY_BUFFER, null); } globalThis.updateCubes = updateCubes globalThis.cleanupFirstChunks = () => { - allBlocks = [] + allSides = [] gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); // empty the buffer gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NumberOfCube * 3), gl.STATIC_DRAW); // todo @@ -266,7 +315,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im } globalThis.fullReset = () => { - allBlocks = [] + allSides = [] globalThis.cleanupFirstChunks() lastNotUpdatedIndex = undefined lastNotUpdatedArrSize = undefined @@ -310,7 +359,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.enable(gl.DEPTH_TEST) gl.frontFace(gl.CCW) - gl.enable(gl.CULL_FACE) + gl.enable(gl.CULL_FACE) gl.enable(gl.BLEND) gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); @@ -359,7 +408,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im camera.updateMatrix() if (!globalThis.stopRendering) { - gl.drawArraysInstanced(gl.TRIANGLES, 0, 36, isPlayground ? NumberOfCube : allBlocks.length); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 6 * (isPlayground ? NumberOfCube : allSides.length)); } //gl.bindVertexArray(null) @@ -427,16 +476,19 @@ onmessage = function (e) { newHeight = e.data.newHeight } if (e.data.type === 'addBlocksSection') { - const currentLength = allBlocks.length; + const currentLength = allSides.length; // in: object - name, out: [x, y, z, name] - const newData = Object.entries(e.data.data.blocks).map(([key, value]) => { + const newData = Object.entries(e.data.data.blocks).flatMap(([key, value]) => { const [x, y, z] = key.split(',').map(Number) - return [x, y, z, value as BlockType] + const block = value as BlockType + return block.sides.map((side) => { + return [x, y, z, side] as [number, number, number, BlockFaceType] + }) }) // find freeIndexes if possible const freeArea = freeArrayIndexes.find(([startIndex, endIndex]) => endIndex - startIndex >= newData.length) chunksArrIndexes[e.data.key] = [currentLength, currentLength + newData.length] - allBlocks.push(...newData) + allSides.push(...newData) lastNotUpdatedIndex ??= currentLength // updateCubes?.(currentLength) } @@ -446,15 +498,15 @@ onmessage = function (e) { lastNotUpdatedArrSize = undefined } if (e.data.type === 'removeBlocksSection') { - const [startIndex, endIndex] = chunksArrIndexes[e.data.key] - freeArrayIndexes.push([startIndex, endIndex]) - - // merge freeArrayIndexes TODO - if (freeArrayIndexes.at(-1)[0] === freeArrayIndexes.at(-2)?.[1]) { - const [startIndex, endIndex] = freeArrayIndexes.pop()! - const [startIndex2, endIndex2] = freeArrayIndexes.pop()! - freeArrayIndexes.push([startIndex2, endIndex]) - } + // const [startIndex, endIndex] = chunksArrIndexes[e.data.key] + // freeArrayIndexes.push([startIndex, endIndex]) + + // // merge freeArrayIndexes TODO + // if (freeArrayIndexes.at(-1)[0] === freeArrayIndexes.at(-2)?.[1]) { + // const [startIndex, endIndex] = freeArrayIndexes.pop()! + // const [startIndex2, endIndex2] = freeArrayIndexes.pop()! + // freeArrayIndexes.push([startIndex2, endIndex]) + // } } if (e.data.type === 'camera') { camera.rotation.set(e.data.camera.rotation.x, e.data.camera.rotation.y, e.data.camera.rotation.z, 'ZYX') diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 853c0414d..89a16befc 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -18,7 +18,7 @@ for (const key of Object.keys(tintsData)) { tints[key] = prepareTints(tintsData[key]) } -function prepareTints (tints) { +function prepareTints(tints) { const map = new Map() const defaultValue = tintToGl(tints.default) for (let { keys, color } of tints.data) { @@ -34,7 +34,7 @@ function prepareTints (tints) { }) } -function tintToGl (tint) { +function tintToGl(tint) { const r = (tint >> 16) & 0xff const g = (tint >> 8) & 0xff const b = tint & 0xff @@ -110,7 +110,7 @@ const elemFaces = { } } -function getLiquidRenderHeight (world, block, type) { +function getLiquidRenderHeight(world, block, type) { if (!block || block.type !== type) return 1 / 9 if (block.metadata === 0) { // source block const blockAbove = world.getBlock(block.position.offset(0, 1, 0)) @@ -120,7 +120,7 @@ function getLiquidRenderHeight (world, block, type) { return ((block.metadata >= 8 ? 8 : 7 - block.metadata) + 1) / 9 } -function renderLiquid (world, cursor, texture, type, biome, water, attr) { +function renderLiquid(world, cursor, texture, type, biome, water, attr) { const heights: number[] = [] for (let z = -1; z <= 1; z++) { for (let x = -1; x <= 1; x++) { @@ -170,17 +170,17 @@ function renderLiquid (world, cursor, texture, type, biome, water, attr) { } } -function vecadd3 (a, b) { +function vecadd3(a, b) { if (!b) return a return [a[0] + b[0], a[1] + b[1], a[2] + b[2]] } -function vecsub3 (a, b) { +function vecsub3(a, b) { if (!b) return a return [a[0] - b[0], a[1] - b[1], a[2] - b[2]] } -function matmul3 (matrix, vector): [number, number, number] { +function matmul3(matrix, vector): [number, number, number] { if (!matrix) return vector return [ matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2], @@ -189,7 +189,7 @@ function matmul3 (matrix, vector): [number, number, number] { ] } -function matmulmat3 (a, b) { +function matmulmat3(a, b) { const te = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] const a11 = a[0][0]; const a12 = a[1][0]; const a13 = a[2][0] @@ -215,7 +215,7 @@ function matmulmat3 (a, b) { return te } -function buildRotationMatrix (axis, degree) { +function buildRotationMatrix(axis, degree) { const radians = degree / 180 * Math.PI const cos = Math.cos(radians) const sin = Math.sin(radians) @@ -239,7 +239,7 @@ function buildRotationMatrix (axis, degree) { return matrix } -function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr, globalMatrix, globalShift, block: Block, biome) { +function renderElement(world: World, cursor: Vec3, element, doAO: boolean, attr, globalMatrix, globalShift, block: Block, biome) { const cullIfIdentical = block.name.indexOf('glass') >= 0 for (const face in element.faces) { @@ -375,7 +375,233 @@ function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr } } -export function getSectionGeometry (sx, sy, sz, world: World) { +const facesIndexes = { + down: 0, + up: 1, + north: 2, + south: 3, + west: 4, + east: 5 +} + +type AttrType = { + blocks: { + [pos: string]: BlockType + } +} + +let textureName = undefined +let tint +const getResult = (biome, block, side: string): number => { + const facesOrTexture = findTextureInBlockStates(block.name); + if (!facesOrTexture) return 0 // todo + let result + if ('u' in facesOrTexture) { + result = facesOrTexture + } else { + result = facesOrTexture?.[side]?.texture + const tintindex = facesOrTexture?.[side]?.tintindex + if (tintindex === 0) { + if (block.name === 'redstone_wire') { + tint = tints.redstone[`${block.getProperties().power}`] + } else if (block.name === 'birch_leaves' || + block.name === 'spruce_leaves' || + block.name === 'lily_pad') { + tint = tints.constant[block.name] + } else if (block.name.includes('leaves') || block.name === 'vine') { + tint = tints.foliage[biome] + } else { + tint = tints.grass[biome] + } + } + } + if (!result) return 0 // todo + if (result.textureName) { + textureName = result.textureName + } + return uvToTextureIndex(result.u, result.v) - (result.su < 0 ? 1 : 0) - (result.sv < 0 ? 1 : 0) +} +function uvToTextureIndex(u, v) { + const textureWidth = textureSize + const textureHeight = textureSize + const tileSize = 16; + // Convert UV coordinates to pixel coordinates + let x = u * textureWidth; + let y = v * textureHeight; + + // Convert pixel coordinates to tile index + const tileX = Math.floor(x / tileSize); + const tileY = Math.floor(y / tileSize); + + // Calculate texture index + const textureIndex = tileY * (textureWidth / tileSize) + tileX; + + return textureIndex; +} + +const findTextureInBlockStates = (name): any => { + const vars = blockStates[name]?.variants + if (!vars) return blockStates[name]?.multipart?.[0]?.apply?.[0]?.model?.elements?.[0]?.faces?.south?.texture + let firstVar = Object.values(vars)[0] as any + if (Array.isArray(firstVar)) firstVar = firstVar[0] + if (!firstVar) return + const [element] = firstVar.model?.elements + if (!element) return firstVar.model?.textures?.particle + if (!element/* || !(element?.from.every(a => a === 0) && element?.to.every(a => a === 16)) */) return + return element.faces +} + +function renderElementNew(world: World, cursor: Vec3, element, doAO: boolean, attr: AttrType, globalMatrix, globalShift, block: Block, biome) { + const cullIfIdentical = block.name.indexOf('glass') >= 0 + + for (const face in element.faces) { + const faceIndex = facesIndexes[face] + + const eFace = element.faces[face] + const { corners, mask1, mask2 } = elemFaces[face] + const dir = matmul3(globalMatrix, elemFaces[face].dir) + + if (eFace.cullface) { + const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) + if (neighbor) { + if (cullIfIdentical && neighbor.type === block.type) continue + if (!neighbor.transparent && neighbor.isCube) continue + } else { + continue + } + } + + const minx = element.from[0] + const miny = element.from[1] + const minz = element.from[2] + const maxx = element.to[0] + const maxy = element.to[1] + const maxz = element.to[2] + + const u = eFace.texture.u + const v = eFace.texture.v + const su = eFace.texture.su + const sv = eFace.texture.sv + + // const ndx = Math.floor(attr.positions.length / 3) + + let tint = [1, 1, 1] as [number, number, number] + if (eFace.tintindex !== undefined) { + if (eFace.tintindex === 0) { + if (block.name === 'redstone_wire') { + tint = tints.redstone[`${block.getProperties().power}`] + } else if (block.name === 'birch_leaves' || + block.name === 'spruce_leaves' || + block.name === 'lily_pad') { + tint = tints.constant[block.name] + } else if (block.name.includes('leaves') || block.name === 'vine') { + tint = tints.foliage[biome] + } else { + tint = tints.grass[biome] + } + } + } + + // UV rotation + const r = eFace.rotation || 0 + const uvcs = Math.cos(r * Math.PI / 180) + const uvsn = -Math.sin(r * Math.PI / 180) + + let localMatrix = null as any + let localShift = null as any + + if (element.rotation) { + localMatrix = buildRotationMatrix( + element.rotation.axis, + element.rotation.angle + ) + + localShift = vecsub3( + element.rotation.origin, + matmul3( + localMatrix, + element.rotation.origin + ) + ) + } + + const cursorPos = `${cursor.x},${cursor.y},${cursor.z}` + attr.blocks[cursorPos] ??= { + sides: [] + } + attr.blocks[cursorPos].sides.push({ + face: faceIndex, + textureIndex: getResult(biome, block, face), + isTransparent: block.transparent, // todo + textureName, + tint + }) + + // const aos: number[] = [] + // for (const pos of corners) { + // let vertex = [ + // (pos[0] ? maxx : minx), + // (pos[1] ? maxy : miny), + // (pos[2] ? maxz : minz) + // ] + + // vertex = vecadd3(matmul3(localMatrix, vertex), localShift) + // vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) + // vertex = vertex.map(v => v / 16) + + // attr.positions.push( + // vertex[0] + (cursor.x & 15) - 8, + // vertex[1] + (cursor.y & 15) - 8, + // vertex[2] + (cursor.z & 15) - 8 + // ) + + // attr.normals.push(...dir) + + // const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5 + // const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5 + // attr.uvs.push(baseu * su + u, basev * sv + v) + + // let light = 1 + // if (doAO) { + // const dx = pos[0] * 2 - 1 + // const dy = pos[1] * 2 - 1 + // const dz = pos[2] * 2 - 1 + // const cornerDir = matmul3(globalMatrix, [dx, dy, dz]) + // const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]]) + // const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]]) + // const side1 = world.getBlock(cursor.offset(...side1Dir)) + // const side2 = world.getBlock(cursor.offset(...side2Dir)) + // const corner = world.getBlock(cursor.offset(...cornerDir)) + + // const side1Block = world.shouldMakeAo(side1) ? 1 : 0 + // const side2Block = world.shouldMakeAo(side2) ? 1 : 0 + // const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0 + + // // TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block) + + // const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock)) + // light = (ao + 1) / 4 + // aos.push(ao) + // } + + // attr.colors.push(tint[0] * light, tint[1] * light, tint[2] * light) + // } + + // if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { + // attr.indices.push( + // ndx, ndx + 3, ndx + 2, + // ndx, ndx + 1, ndx + 3 + // ) + // } else { + // attr.indices.push( + // ndx, ndx + 1, ndx + 2, + // ndx + 2, ndx + 1, ndx + 3 + // ) + // } + } +} + +export function getSectionGeometry(sx, sy, sz, world: World) { const attr = { sx: sx + 8, sy: sy + 8, @@ -418,125 +644,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { if (block.variant === undefined) { block.variant = getModelVariants(block) } - - if (block.name !== 'water' && /* && block.name !== 'lava' *//* && block.isCube */block.name !== 'air') { - let globalMatrix = null as any - let globalShift = null as any - - if (block.variant?.model) { - for (const axis of ['x', 'y', 'z']) { - if (axis in block.variant) { - if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -block.variant[axis]) - else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -block.variant[axis])) - } - } - } - - if (globalMatrix) { - globalShift = [8, 8, 8] - globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) - } - - // full cube rendering - for (const face in elemFaces) { - const cullIfIdentical = block.name.indexOf('glass') >= 0 - - // const eFace = element.faces[face] - const { corners, mask1, mask2 } = elemFaces[face] - const dir = matmul3(globalMatrix, elemFaces[face].dir) - - if (/* eFace.cullface */true) { - const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) - if (neighbor) { - if (cullIfIdentical && neighbor.type === block.type) continue - if (!neighbor.transparent && neighbor.isCube) continue - } else { - continue - } - } - - const pos = block.position - - const findTextureInBlockStates = (name): any => { - const vars = blockStates[name]?.variants - if (!vars) return blockStates[name]?.multipart?.[0]?.apply?.[0]?.model?.elements?.[0]?.faces?.south?.texture - let firstVar = Object.values(vars)[0] as any - if (Array.isArray(firstVar)) firstVar = firstVar[0] - if (!firstVar) return - const [element] = firstVar.model?.elements - if (!element) return firstVar.model?.textures?.particle - if (!element/* || !(element?.from.every(a => a === 0) && element?.to.every(a => a === 16)) */) return - return element.faces - } - - let textureName = undefined - let tint - const getResult = (side: string): number => { - const facesOrTexture = findTextureInBlockStates(block.name); - if (!facesOrTexture) return 0 // todo - let result - if ('u' in facesOrTexture) { - result = facesOrTexture - } else { - result = facesOrTexture?.[side]?.texture - const tintindex = facesOrTexture?.[side]?.tintindex - if (tintindex === 0) { - if (block.name === 'redstone_wire') { - tint = tints.redstone[`${block.getProperties().power}`] - } else if (block.name === 'birch_leaves' || - block.name === 'spruce_leaves' || - block.name === 'lily_pad') { - tint = tints.constant[block.name] - } else if (block.name.includes('leaves') || block.name === 'vine') { - tint = tints.foliage[biome] - } else { - tint = tints.grass[biome] - } - } - } - if (!result) return 0 // todo - if (result.textureName) { - textureName = result.textureName - } - return uvToTextureIndex(result.u, result.v) - (result.su < 0 ? 1 : 0) - (result.sv < 0 ? 1 : 0) - } - function uvToTextureIndex (u, v) { - const textureWidth = textureSize - const textureHeight = textureSize - const tileSize = 16; - // Convert UV coordinates to pixel coordinates - let x = u * textureWidth; - let y = v * textureHeight; - - // Convert pixel coordinates to tile index - const tileX = Math.floor(x / tileSize); - const tileY = Math.floor(y / tileSize); - - // Calculate texture index - const textureIndex = tileY * (textureWidth / tileSize) + tileX; - - return textureIndex; - } - // back, front, left, right, top, bottom - const textures = [ - getResult('north'), - getResult('south'), - getResult('west'), - getResult('east'), - getResult('down'), - getResult('up') - ] - if (textures.every(t => t === 0)) continue - if (pos.y <= 1) continue // TODO!! - attr.blocks[`${pos.x},${pos.y},${pos.z}`] = { - textureIndex: textures, - textureName, - tint, - isTransparent: block.name.includes('glass') || block.name.includes('grass') || block.name.includes('leaves') || block.name.includes('fire') // todo - } satisfies BlockType - } - - } + // if (block.name === 'water') { // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) // } else if (block.name === 'lava') { @@ -562,6 +670,32 @@ export function getSectionGeometry (sx, sy, sz, world: World) { // } // } + for (const variant of block.variant) { + if (!variant || !variant.model) continue + + // if (block.name === 'water') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) + //} else if (block.name === 'lava') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) + let globalMatrix = null as any + let globalShift = null as any + + for (const axis of ['x', 'y', 'z']) { + if (axis in variant) { + if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) + else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) + } + } + + if (globalMatrix) { + globalShift = [8, 8, 8] + globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) + } + + for (const element of variant.model.elements) { + renderElementNew(world, cursor, element, variant.model.ao, attr as any, globalMatrix, globalShift, block, biome) + } + } } } } @@ -596,7 +730,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { return attr } -function parseProperties (properties) { +function parseProperties(properties) { if (typeof properties === 'object') { return properties } const json = {} @@ -607,7 +741,7 @@ function parseProperties (properties) { return json } -function matchProperties (block: Block, /* to match against */properties: Record & { OR }) { +function matchProperties(block: Block, /* to match against */properties: Record & { OR }) { if (!properties) { return true } properties = parseProperties(properties) @@ -625,7 +759,7 @@ function matchProperties (block: Block, /* to match against */properties: Record return true } -function getModelVariants (block: import('prismarine-block').Block) { +function getModelVariants(block: import('prismarine-block').Block) { // air, cave_air, void_air and so on... // full list of invisible & special blocks https://minecraft.wiki/w/Model#Blocks_and_fluids if (block.name === '' || block.name === 'air' || block.name.endsWith('_air')) return [] diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index f43d3886e..b5652cc26 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -116,6 +116,7 @@ export abstract class WorldRendererCommon this.material.map.onUpdate = () => { this.downloadedTextureImage = this.material.map!.image } + // TODO const loadBlockStates = async () => { return new Promise(resolve => { if (this.customBlockStatesData) return resolve(this.customBlockStatesData) diff --git a/src/index.ts b/src/index.ts index 3e7d951da..67fb798da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -864,6 +864,7 @@ downloadAndOpenFile().then((downloadAction) => { const initialLoader = document.querySelector('.initial-loader') as HTMLElement | null if (initialLoader) { initialLoader.style.opacity = '0' + initialLoader.style.pointerEvents = 'none' } window.pageLoaded = true From e3205ed9c61939d2552aaca40aa1f715375ef574 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 01:42:32 +0300 Subject: [PATCH 66/86] prev commit fixes, fix bg, add reset --- prismarine-viewer/examples/webglRenderer.ts | 2 +- .../examples/webglRendererWorker.ts | 40 +++++++---- prismarine-viewer/viewer/lib/models.ts | 71 ++++++++++--------- prismarine-viewer/viewer/lib/world.ts | 1 + .../viewer/lib/worldrendererWebgl.ts | 12 +++- src/controls.ts | 4 ++ 6 files changed, 79 insertions(+), 51 deletions(-) diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 8fc08a19d..66280c70b 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -6,7 +6,7 @@ let worker declare const viewer: Viewer -const sendWorkerMessage = (message: any, transfer?: Transferable[]) => { +export const sendWorkerMessage = (message: any, transfer?: Transferable[]) => { worker.postMessage(message, transfer) // replacable by onmessage } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index b82c2329c..a9b2fb272 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -117,7 +117,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im sideBiomeColor[i + 1] = (Math.random()); sideBiomeColor[i + 2] = (Math.random()); for (let j = 1; j <= 6; j++) { - + if (j != 6) { sidePositions[j * 3 + i] = sidePositions[i] sidePositions[j * 3 + i + 1] = sidePositions[i + 1] @@ -133,7 +133,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im sideTextureIndices[i / 3 + j - 1] = 1; } - // sidePositions[i +3] = sidePositions[i] + // sidePositions[i +3] = sidePositions[i] // sidePositions[i + 4] = sidePositions[i + 2] // sidePositions[i + 5] = sidePositions[i + 1] @@ -249,6 +249,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im globalThis.allSidesSize = allSides.length sidePositions = new Float32Array(newSides.length * 3) sideTextureIndices = new Float32Array(newSides.length * 1); + sideIndexes = new Float32Array(newSides.length * 1); sideBiomeColor = new Float32Array(newSides.length * 3); for (let i = 0; i < newSides.length * 3; i += 3) { sidePositions[i] = newSides[i / 3][0] @@ -301,7 +302,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im } globalThis.updateCubes = updateCubes - globalThis.cleanupFirstChunks = () => { + const cleanupFirstChunks = () => { allSides = [] gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); // empty the buffer @@ -314,9 +315,8 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindBuffer(gl.ARRAY_BUFFER, null); } - globalThis.fullReset = () => { - allSides = [] - globalThis.cleanupFirstChunks() + fullReset = () => { + cleanupFirstChunks() lastNotUpdatedIndex = undefined lastNotUpdatedArrSize = undefined } @@ -347,9 +347,6 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, textureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureBitmap); - //gl.generateMipmap(gl.TEXTURE_2D); - - //gl.generateMipmap(gl.TEXTURE_2D); gl.useProgram(program) @@ -359,7 +356,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.enable(gl.DEPTH_TEST) gl.frontFace(gl.CCW) - gl.enable(gl.CULL_FACE) + gl.enable(gl.CULL_FACE) gl.enable(gl.BLEND) gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); @@ -396,7 +393,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im } gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) - gl.clearColor(0.6784313725490196, 0.8470588235294118, 0.9019607843137255, 0.0); + gl.clearColor(0.6784313725490196, 0.8470588235294118, 0.9019607843137255, 1); gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.DEPTH_BUFFER_BIT) @@ -408,7 +405,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im camera.updateMatrix() if (!globalThis.stopRendering) { - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 6 * (isPlayground ? NumberOfCube : allSides.length)); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, (isPlayground ? NumberOfCube * 6 : allSides.length)); } //gl.bindVertexArray(null) @@ -424,6 +421,8 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im return canvas } +let fullReset + const createProgram = (gl: WebGL2RenderingContext, vertexShader: string, fragmentShader: string) => { const createShader = (gl: WebGL2RenderingContext, type: number, source: string) => { const shaderName = type === gl.VERTEX_SHADER ? 'vertex' : 'fragment' @@ -487,6 +486,20 @@ onmessage = function (e) { }) // find freeIndexes if possible const freeArea = freeArrayIndexes.find(([startIndex, endIndex]) => endIndex - startIndex >= newData.length) + if (freeArea) { + const [startIndex, endIndex] = freeArea + allSides.splice(startIndex, newData.length, ...newData) + lastNotUpdatedIndex ??= startIndex + const freeAreaIndex = freeArrayIndexes.indexOf(freeArea) + freeArrayIndexes[freeAreaIndex] = [startIndex + newData.length, endIndex] + if (freeArrayIndexes[freeAreaIndex][0] >= freeArrayIndexes[freeAreaIndex][1]) { + freeArrayIndexes.splice(freeAreaIndex, 1) + // todo merge + } + lastNotUpdatedArrSize = newData.length + console.log('using free area', freeArea) + } + chunksArrIndexes[e.data.key] = [currentLength, currentLength + newData.length] allSides.push(...newData) lastNotUpdatedIndex ??= currentLength @@ -525,6 +538,9 @@ onmessage = function (e) { animationTick = e.data.tick % 20 // todo update automatically in worker } } + if (e.data.type === 'fullReset') { + fullReset() + } } setInterval(() => { diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 89a16befc..834eb70fc 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -18,7 +18,7 @@ for (const key of Object.keys(tintsData)) { tints[key] = prepareTints(tintsData[key]) } -function prepareTints(tints) { +function prepareTints (tints) { const map = new Map() const defaultValue = tintToGl(tints.default) for (let { keys, color } of tints.data) { @@ -34,7 +34,7 @@ function prepareTints(tints) { }) } -function tintToGl(tint) { +function tintToGl (tint) { const r = (tint >> 16) & 0xff const g = (tint >> 8) & 0xff const b = tint & 0xff @@ -110,7 +110,7 @@ const elemFaces = { } } -function getLiquidRenderHeight(world, block, type) { +function getLiquidRenderHeight (world, block, type) { if (!block || block.type !== type) return 1 / 9 if (block.metadata === 0) { // source block const blockAbove = world.getBlock(block.position.offset(0, 1, 0)) @@ -120,7 +120,7 @@ function getLiquidRenderHeight(world, block, type) { return ((block.metadata >= 8 ? 8 : 7 - block.metadata) + 1) / 9 } -function renderLiquid(world, cursor, texture, type, biome, water, attr) { +function renderLiquid (world, cursor, texture, type, biome, water, attr) { const heights: number[] = [] for (let z = -1; z <= 1; z++) { for (let x = -1; x <= 1; x++) { @@ -170,17 +170,17 @@ function renderLiquid(world, cursor, texture, type, biome, water, attr) { } } -function vecadd3(a, b) { +function vecadd3 (a, b) { if (!b) return a return [a[0] + b[0], a[1] + b[1], a[2] + b[2]] } -function vecsub3(a, b) { +function vecsub3 (a, b) { if (!b) return a return [a[0] - b[0], a[1] - b[1], a[2] - b[2]] } -function matmul3(matrix, vector): [number, number, number] { +function matmul3 (matrix, vector): [number, number, number] { if (!matrix) return vector return [ matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2], @@ -189,7 +189,7 @@ function matmul3(matrix, vector): [number, number, number] { ] } -function matmulmat3(a, b) { +function matmulmat3 (a, b) { const te = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] const a11 = a[0][0]; const a12 = a[1][0]; const a13 = a[2][0] @@ -215,7 +215,7 @@ function matmulmat3(a, b) { return te } -function buildRotationMatrix(axis, degree) { +function buildRotationMatrix (axis, degree) { const radians = degree / 180 * Math.PI const cos = Math.cos(radians) const sin = Math.sin(radians) @@ -239,7 +239,7 @@ function buildRotationMatrix(axis, degree) { return matrix } -function renderElement(world: World, cursor: Vec3, element, doAO: boolean, attr, globalMatrix, globalShift, block: Block, biome) { +function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr, globalMatrix, globalShift, block: Block, biome) { const cullIfIdentical = block.name.indexOf('glass') >= 0 for (const face in element.faces) { @@ -421,7 +421,7 @@ const getResult = (biome, block, side: string): number => { } return uvToTextureIndex(result.u, result.v) - (result.su < 0 ? 1 : 0) - (result.sv < 0 ? 1 : 0) } -function uvToTextureIndex(u, v) { +function uvToTextureIndex (u, v) { const textureWidth = textureSize const textureHeight = textureSize const tileSize = 16; @@ -451,7 +451,7 @@ const findTextureInBlockStates = (name): any => { return element.faces } -function renderElementNew(world: World, cursor: Vec3, element, doAO: boolean, attr: AttrType, globalMatrix, globalShift, block: Block, biome) { +function renderElementNew (world: World, cursor: Vec3, element, doAO: boolean, attr: AttrType, globalMatrix, globalShift, block: Block, biome) { const cullIfIdentical = block.name.indexOf('glass') >= 0 for (const face in element.faces) { @@ -601,7 +601,7 @@ function renderElementNew(world: World, cursor: Vec3, element, doAO: boolean, at } } -export function getSectionGeometry(sx, sy, sz, world: World) { +export function getSectionGeometry (sx, sy, sz, world: World) { const attr = { sx: sx + 8, sy: sy + 8, @@ -644,7 +644,7 @@ export function getSectionGeometry(sx, sy, sz, world: World) { if (block.variant === undefined) { block.variant = getModelVariants(block) } - + // if (block.name === 'water') { // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) // } else if (block.name === 'lava') { @@ -673,28 +673,28 @@ export function getSectionGeometry(sx, sy, sz, world: World) { for (const variant of block.variant) { if (!variant || !variant.model) continue - // if (block.name === 'water') { - // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) + // if (block.name === 'water') { + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) //} else if (block.name === 'lava') { - // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) - let globalMatrix = null as any - let globalShift = null as any - - for (const axis of ['x', 'y', 'z']) { - if (axis in variant) { - if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) - else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) - } + // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, false, attr) + let globalMatrix = null as any + let globalShift = null as any + + for (const axis of ['x', 'y', 'z']) { + if (axis in variant) { + if (!globalMatrix) globalMatrix = buildRotationMatrix(axis, -variant[axis]) + else globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -variant[axis])) } + } - if (globalMatrix) { - globalShift = [8, 8, 8] - globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) - } + if (globalMatrix) { + globalShift = [8, 8, 8] + globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) + } - for (const element of variant.model.elements) { - renderElementNew(world, cursor, element, variant.model.ao, attr as any, globalMatrix, globalShift, block, biome) - } + const elements = variant.model.elements; + const element = elements[0] + if (element) renderElementNew(world, cursor, element, variant.model.ao, attr as any, globalMatrix, globalShift, block, biome) } } } @@ -726,11 +726,12 @@ export function getSectionGeometry(sx, sy, sz, world: World) { attr.normals = new Float32Array(attr.normals) as any attr.colors = new Float32Array(attr.colors) as any attr.uvs = new Float32Array(attr.uvs) as any + if (Object.keys(attr.blocks).length === 0) return attr return attr } -function parseProperties(properties) { +function parseProperties (properties) { if (typeof properties === 'object') { return properties } const json = {} @@ -741,7 +742,7 @@ function parseProperties(properties) { return json } -function matchProperties(block: Block, /* to match against */properties: Record & { OR }) { +function matchProperties (block: Block, /* to match against */properties: Record & { OR }) { if (!properties) { return true } properties = parseProperties(properties) @@ -759,7 +760,7 @@ function matchProperties(block: Block, /* to match against */properties: Record< return true } -function getModelVariants(block: import('prismarine-block').Block) { +function getModelVariants (block: import('prismarine-block').Block) { // air, cave_air, void_air and so on... // full list of invisible & special blocks https://minecraft.wiki/w/Model#Blocks_and_fluids if (block.name === '' || block.name === 'air' || block.name.endsWith('_air')) return [] diff --git a/prismarine-viewer/viewer/lib/world.ts b/prismarine-viewer/viewer/lib/world.ts index a4ffd69c6..d0075dce0 100644 --- a/prismarine-viewer/viewer/lib/world.ts +++ b/prismarine-viewer/viewer/lib/world.ts @@ -18,6 +18,7 @@ function posInChunk (pos) { } function isCube (shapes) { + return true if (!shapes || shapes.length !== 1) return false const shape = shapes[0] return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1 diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts index 4556d953c..8d5518ab4 100644 --- a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -1,4 +1,4 @@ -import { addBlocksSection, removeBlocksSection } from '../../examples/webglRenderer' +import { addBlocksSection, removeBlocksSection, sendWorkerMessage } from '../../examples/webglRenderer' import type { WebglData } from '../prepare/webglData' import { loadJSON } from './utils.web' import { WorldRendererCommon } from './worldrendererCommon' @@ -28,7 +28,7 @@ export class WorldRendererWebgl extends WorldRendererCommon { } handleWorkerMessage (data: any): void { - if (data.type === 'geometry') { + if (data.type === 'geometry' && Object.keys(data.geometry.blocks).length) { const chunkCoords = data.key.split(',') if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return @@ -39,6 +39,12 @@ export class WorldRendererWebgl extends WorldRendererCommon { } } + chunksReset () { + sendWorkerMessage({ + type: 'fullReset' + }) + } + updatePosDataChunk (key: string) { } @@ -55,7 +61,7 @@ export class WorldRendererWebgl extends WorldRendererCommon { removeColumn (x, z) { - return + console.log('removeColumn', x, z) super.removeColumn(x, z) for (const key of Object.keys(this.newChunks)) { const [xSec, _ySec, zSec] = key.split(',').map(Number) diff --git a/src/controls.ts b/src/controls.ts index 48c051791..cd0afad6e 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -288,6 +288,10 @@ export const f3Keybinds = [ console.warn('forcefully removed chunk from scene') } } + + viewer.world.chunksReset() // todo + viewer.world.newChunks = {} + if (localServer) { //@ts-expect-error not sure why it is private... maybe revisit api? localServer.players[0].world.columns = {} From ff5ca18a2222f0adfba5cc4724e65836d1d9498f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Wed, 10 Apr 2024 01:48:14 +0300 Subject: [PATCH 67/86] KAVOfasdfasdf --- prismarine-viewer/examples/webglRendererWorker.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index b82c2329c..16adcbe2a 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -97,7 +97,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im - let NumberOfCube = isPlayground ? 1_000_000 : 5_000_000 + let NumberOfCube = isPlayground ? 10_000 : 5_000_000 sidePositions = new Float32Array(NumberOfCube * 3 * 6) let sideTextureIndices = new Float32Array(NumberOfCube * 1 * 6); @@ -106,7 +106,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // write random coordinates to cube positions xyz ten cubes; - if (false) { + if (true) { for (let i = 0; i < NumberOfCube * 18; i += 18) { sidePositions[i] = Math.floor(Math.random() * 1000) - 500; @@ -129,8 +129,8 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im } sideIndexes[i / 3 + j - 1] = j - 1; - //sideTextureIndices[i / 3 + j - 1] = Math.floor(Math.random() * 800); - sideTextureIndices[i / 3 + j - 1] = 1; + sideTextureIndices[i / 3 + j - 1] = Math.floor(Math.random() * 800); + //sideTextureIndices[i / 3 + j - 1] = 1; } // sidePositions[i +3] = sidePositions[i] @@ -380,7 +380,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindTexture(gl.TEXTURE_2D, texture1); gl.bindVertexArray(VAO) - + gl.useProgram(program) updateSize(gl.canvas.width, gl.canvas.height) const renderLoop = (performance) => { requestAnimationFrame(renderLoop) @@ -393,14 +393,15 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im newWidth = undefined newHeight = undefined updateSize(gl.canvas.width, gl.canvas.height) + + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) } - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) + gl.clearColor(0.6784313725490196, 0.8470588235294118, 0.9019607843137255, 0.0); gl.clear(gl.COLOR_BUFFER_BIT) gl.clear(gl.DEPTH_BUFFER_BIT) - gl.useProgram(program) gl.uniformMatrix4fv(ViewUniform, false, camera.matrix.invert().elements); gl.uniformMatrix4fv(ProjectionUniform, false, camera.projectionMatrix.elements); From a5f0af8e6bfb75e9c0fecc7586e465b9282755f7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 02:33:36 +0300 Subject: [PATCH 68/86] [deploy] fix singleplayer --- prismarine-viewer/examples/_VertexShader.vert | 18 +- .../examples/webglRendererWorker.ts | 7 +- prismarine-viewer/viewer/lib/models.ts | 298 ++++++++++-------- .../viewer/lib/worldrendererWebgl.ts | 1 + 4 files changed, 176 insertions(+), 148 deletions(-) diff --git a/prismarine-viewer/examples/_VertexShader.vert b/prismarine-viewer/examples/_VertexShader.vert index 9410c1c67..0acdacf94 100644 --- a/prismarine-viewer/examples/_VertexShader.vert +++ b/prismarine-viewer/examples/_VertexShader.vert @@ -7,7 +7,7 @@ layout (location = 3) in vec3 aOffset; layout (location = 4) in float aTextureIndex; layout (location = 6) in vec3 aBiomeColor; -//#Define +//#Define out vec2 TexCoord; flat out float TextureIndex; @@ -43,7 +43,7 @@ void main() TexCoord = vec2((1.0f-aTexCoord.x), (1.0 - aTexCoord.y)); //TextureIndex = aTextureIndex.x; //TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; - + TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(-90.0f))).xyz; break; case 1: @@ -58,20 +58,24 @@ void main() case 3: //TextureIndex = aTextureIndex.z; //TexCoord = vec2(aTexCoord.y, (1.0f - aTexCoord.x)); - + TexCoord = vec2(aTexCoord); //TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(-90.0f))).xyz; - TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; + //TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; + TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(180.0f))).xyz; break; case 4: //TextureIndex = aTextureIndexPlus.x; //TransitionedPos = vec4(aPos,0.0f).xyz; - TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(-90.0f))).xyz; + //TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(-90.0f))).xyz; + TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; break; case 5: //TextureIndex = aTextureIndexPlus.y; //TransitionedPos = vec4(aPos,0.0f).xyz; - TexCoord = vec2(aTexCoord); - TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(180.0f))).xyz; + + //TransitionedPos = (vec4(aPos,0.0f) *rotationX(radians(180.0f))).xyz; + //TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(90.0f))).xyz; + TransitionedPos = (vec4(aPos,0.0f) *rotationY(radians(-90.0f))).xyz; break; } TextureIndex += float(tick); diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index e062f8640..fa36a816c 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -32,6 +32,7 @@ const updateSize = (width, height) => { export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: ImageBitmapSource, isPlayground: boolean, FragShaderOverride?) => { + isPlayground = false // blockStates = blockStatesJson const textureBitmap = await createImageBitmap(imageBlob) const textureWidth = textureBitmap.width @@ -106,7 +107,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // write random coordinates to cube positions xyz ten cubes; - if (true) { + if (isPlayground) { for (let i = 0; i < NumberOfCube * 18; i += 18) { sidePositions[i] = Math.floor(Math.random() * 1000) - 500; @@ -243,7 +244,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // up2 const newSides = allSides.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) newSides.sort((a, b) => { - const getScore = (b: BlockFaceType) => b.isTransparent ? 0 : 1 + const getScore = (b: BlockFaceType) => b.isTransparent ? 1 : 0 return getScore(a[3]) - getScore(b[3]) }) globalThis.allSidesSize = allSides.length @@ -393,7 +394,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) } - + gl.clearColor(0.6784313725490196, 0.8470588235294118, 0.9019607843137255, 1); gl.clear(gl.COLOR_BUFFER_BIT) diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index 834eb70fc..b5ef1871c 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -3,6 +3,7 @@ import { BlockStatesOutput } from '../prepare/modelsBuilder' import { World } from './world' import { Block } from 'prismarine-block' import { BlockType } from '../../examples/shared' +import dataBlocks from '../lib/moreBlockDataGenerated.json' const tints: any = {} let blockStates: BlockStatesOutput @@ -239,141 +240,141 @@ function buildRotationMatrix (axis, degree) { return matrix } -function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr, globalMatrix, globalShift, block: Block, biome) { - const cullIfIdentical = block.name.indexOf('glass') >= 0 - - for (const face in element.faces) { - const eFace = element.faces[face] - const { corners, mask1, mask2 } = elemFaces[face] - const dir = matmul3(globalMatrix, elemFaces[face].dir) - - if (eFace.cullface) { - const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) - if (neighbor) { - if (cullIfIdentical && neighbor.type === block.type) continue - if (!neighbor.transparent && neighbor.isCube) continue - } else { - continue - } - } - - const minx = element.from[0] - const miny = element.from[1] - const minz = element.from[2] - const maxx = element.to[0] - const maxy = element.to[1] - const maxz = element.to[2] - - const u = eFace.texture.u - const v = eFace.texture.v - const su = eFace.texture.su - const sv = eFace.texture.sv - - const ndx = Math.floor(attr.positions.length / 3) - - let tint = [1, 1, 1] - if (eFace.tintindex !== undefined) { - if (eFace.tintindex === 0) { - if (block.name === 'redstone_wire') { - tint = tints.redstone[`${block.getProperties().power}`] - } else if (block.name === 'birch_leaves' || - block.name === 'spruce_leaves' || - block.name === 'lily_pad') { - tint = tints.constant[block.name] - } else if (block.name.includes('leaves') || block.name === 'vine') { - tint = tints.foliage[biome] - } else { - tint = tints.grass[biome] - } - } - } - - // UV rotation - const r = eFace.rotation || 0 - const uvcs = Math.cos(r * Math.PI / 180) - const uvsn = -Math.sin(r * Math.PI / 180) - - let localMatrix = null as any - let localShift = null as any - - if (element.rotation) { - localMatrix = buildRotationMatrix( - element.rotation.axis, - element.rotation.angle - ) - - localShift = vecsub3( - element.rotation.origin, - matmul3( - localMatrix, - element.rotation.origin - ) - ) - } - - const aos: number[] = [] - for (const pos of corners) { - let vertex = [ - (pos[0] ? maxx : minx), - (pos[1] ? maxy : miny), - (pos[2] ? maxz : minz) - ] - - vertex = vecadd3(matmul3(localMatrix, vertex), localShift) - vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) - vertex = vertex.map(v => v / 16) - - attr.positions.push( - vertex[0] + (cursor.x & 15) - 8, - vertex[1] + (cursor.y & 15) - 8, - vertex[2] + (cursor.z & 15) - 8 - ) - - attr.normals.push(...dir) - - const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5 - const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5 - attr.uvs.push(baseu * su + u, basev * sv + v) - - let light = 1 - if (doAO) { - const dx = pos[0] * 2 - 1 - const dy = pos[1] * 2 - 1 - const dz = pos[2] * 2 - 1 - const cornerDir = matmul3(globalMatrix, [dx, dy, dz]) - const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]]) - const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]]) - const side1 = world.getBlock(cursor.offset(...side1Dir)) - const side2 = world.getBlock(cursor.offset(...side2Dir)) - const corner = world.getBlock(cursor.offset(...cornerDir)) - - const side1Block = world.shouldMakeAo(side1) ? 1 : 0 - const side2Block = world.shouldMakeAo(side2) ? 1 : 0 - const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0 - - // TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block) - - const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock)) - light = (ao + 1) / 4 - aos.push(ao) - } - - attr.colors.push(tint[0] * light, tint[1] * light, tint[2] * light) - } - - if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { - attr.indices.push( - ndx, ndx + 3, ndx + 2, - ndx, ndx + 1, ndx + 3 - ) - } else { - attr.indices.push( - ndx, ndx + 1, ndx + 2, - ndx + 2, ndx + 1, ndx + 3 - ) - } - } -} +// function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr, globalMatrix, globalShift, block: Block, biome) { +// const cullIfIdentical = block.name.indexOf('glass') >= 0 + +// for (const face in element.faces) { +// const eFace = element.faces[face] +// const { corners, mask1, mask2 } = elemFaces[face] +// const dir = matmul3(globalMatrix, elemFaces[face].dir) + +// if (eFace.cullface) { +// const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) +// if (neighbor) { +// if (cullIfIdentical && neighbor.type === block.type) continue +// if (!neighbor.transparent && neighbor.isCube) continue +// } else { +// continue +// } +// } + +// const minx = element.from[0] +// const miny = element.from[1] +// const minz = element.from[2] +// const maxx = element.to[0] +// const maxy = element.to[1] +// const maxz = element.to[2] + +// const u = eFace.texture.u +// const v = eFace.texture.v +// const su = eFace.texture.su +// const sv = eFace.texture.sv + +// const ndx = Math.floor(attr.positions.length / 3) + +// let tint = [1, 1, 1] +// if (eFace.tintindex !== undefined) { +// if (eFace.tintindex === 0) { +// if (block.name === 'redstone_wire') { +// tint = tints.redstone[`${block.getProperties().power}`] +// } else if (block.name === 'birch_leaves' || +// block.name === 'spruce_leaves' || +// block.name === 'lily_pad') { +// tint = tints.constant[block.name] +// } else if (block.name.includes('leaves') || block.name === 'vine') { +// tint = tints.foliage[biome] +// } else { +// tint = tints.grass[biome] +// } +// } +// } + +// // UV rotation +// const r = eFace.rotation || 0 +// const uvcs = Math.cos(r * Math.PI / 180) +// const uvsn = -Math.sin(r * Math.PI / 180) + +// let localMatrix = null as any +// let localShift = null as any + +// if (element.rotation) { +// localMatrix = buildRotationMatrix( +// element.rotation.axis, +// element.rotation.angle +// ) + +// localShift = vecsub3( +// element.rotation.origin, +// matmul3( +// localMatrix, +// element.rotation.origin +// ) +// ) +// } + +// const aos: number[] = [] +// for (const pos of corners) { +// let vertex = [ +// (pos[0] ? maxx : minx), +// (pos[1] ? maxy : miny), +// (pos[2] ? maxz : minz) +// ] + +// vertex = vecadd3(matmul3(localMatrix, vertex), localShift) +// vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) +// vertex = vertex.map(v => v / 16) + +// attr.positions.push( +// vertex[0] + (cursor.x & 15) - 8, +// vertex[1] + (cursor.y & 15) - 8, +// vertex[2] + (cursor.z & 15) - 8 +// ) + +// attr.normals.push(...dir) + +// const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5 +// const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5 +// attr.uvs.push(baseu * su + u, basev * sv + v) + +// let light = 1 +// if (doAO) { +// const dx = pos[0] * 2 - 1 +// const dy = pos[1] * 2 - 1 +// const dz = pos[2] * 2 - 1 +// const cornerDir = matmul3(globalMatrix, [dx, dy, dz]) +// const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]]) +// const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]]) +// const side1 = world.getBlock(cursor.offset(...side1Dir)) +// const side2 = world.getBlock(cursor.offset(...side2Dir)) +// const corner = world.getBlock(cursor.offset(...cornerDir)) + +// const side1Block = world.shouldMakeAo(side1) ? 1 : 0 +// const side2Block = world.shouldMakeAo(side2) ? 1 : 0 +// const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0 + +// // TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block) + +// const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock)) +// light = (ao + 1) / 4 +// aos.push(ao) +// } + +// attr.colors.push(tint[0] * light, tint[1] * light, tint[2] * light) +// } + +// if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { +// attr.indices.push( +// ndx, ndx + 3, ndx + 2, +// ndx, ndx + 1, ndx + 3 +// ) +// } else { +// attr.indices.push( +// ndx, ndx + 1, ndx + 2, +// ndx + 2, ndx + 1, ndx + 3 +// ) +// } +// } +// } const facesIndexes = { down: 0, @@ -451,8 +452,13 @@ const findTextureInBlockStates = (name): any => { return element.faces } +const isTransparent = (block: Block) => { + return block.transparent || block.material === 'plant' || block.name === 'water' || block.name === 'lava' + // return !!dataBlocks.noOcclusions[block.name] +} + function renderElementNew (world: World, cursor: Vec3, element, doAO: boolean, attr: AttrType, globalMatrix, globalShift, block: Block, biome) { - const cullIfIdentical = block.name.indexOf('glass') >= 0 + const cullIfIdentical = block.name.indexOf('glass') >= 0 || block.name === 'water' || block.name === 'lava' for (const face in element.faces) { const faceIndex = facesIndexes[face] @@ -465,7 +471,7 @@ function renderElementNew (world: World, cursor: Vec3, element, doAO: boolean, a const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) if (neighbor) { if (cullIfIdentical && neighbor.type === block.type) continue - if (!neighbor.transparent && neighbor.isCube) continue + if (!isTransparent(neighbor) && neighbor.isCube) continue } else { continue } @@ -532,7 +538,7 @@ function renderElementNew (world: World, cursor: Vec3, element, doAO: boolean, a attr.blocks[cursorPos].sides.push({ face: faceIndex, textureIndex: getResult(biome, block, face), - isTransparent: block.transparent, // todo + isTransparent: isTransparent(block), // todo textureName, tint }) @@ -645,6 +651,22 @@ export function getSectionGeometry (sx, sy, sz, world: World) { block.variant = getModelVariants(block) } + if (block.name === 'water' || block.name === 'lava') { + const textureParticle = block.variant![0].model.textures.particle + const tex = { texture: textureParticle, cullface: true, tintindex: 0 }; + block.variant![0].model.elements.push({ + from: [0, 0, 0], + to: [16, 16, 16], + faces: { + down: tex, + up: tex, + north: tex, + south: tex, + west: tex, + east: tex + } + }) + } // if (block.name === 'water') { // renderLiquid(world, cursor, variant.model.textures.particle, block.type, biome, true, attr) // } else if (block.name === 'lava') { diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts index 8d5518ab4..6141648c5 100644 --- a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -61,6 +61,7 @@ export class WorldRendererWebgl extends WorldRendererCommon { removeColumn (x, z) { + return console.log('removeColumn', x, z) super.removeColumn(x, z) for (const key of Object.keys(this.newChunks)) { From 50efd7659560b8ee7af0bf5bab296d026e70b5f6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 04:18:52 +0300 Subject: [PATCH 69/86] workaround and potential fix --- .../examples/webglRendererWorker.ts | 26 ++++------------ .../viewer/lib/worldrendererCommon.ts | 30 ++++++++++--------- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index fa36a816c..348d0c4d9 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -98,7 +98,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im - let NumberOfCube = isPlayground ? 10_000 : 5_000_000 + let NumberOfCube = isPlayground ? 10_000 : 1_000_000 sidePositions = new Float32Array(NumberOfCube * 3 * 6) let sideTextureIndices = new Float32Array(NumberOfCube * 1 * 6); @@ -163,7 +163,6 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // cubePositions[0] = 0; // cubePositions[1] = 0; // cubePositions[2] = 0; - let VAO = gl.createVertexArray(); @@ -203,9 +202,11 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 5 * 4, 3 * 4) gl.enableVertexAttribArray(1) - //gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 6 * 4, 5 * 4) - // gl.enableVertexAttribArray(2) - //instance data + gl.enableVertexAttribArray(2); + gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); + gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 4, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.vertexAttribDivisor(2, 1); gl.enableVertexAttribArray(3); gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); @@ -214,19 +215,10 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.vertexAttribDivisor(3, 1); gl.enableVertexAttribArray(4); - // gl.enableVertexAttribArray(5); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); gl.vertexAttribPointer(4, 1, gl.FLOAT, false, 4 * 1, 0); - // gl.vertexAttribPointer(5, 2, gl.FLOAT, false, 4 * 6, 4 * 4); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(4, 1); - // gl.vertexAttribDivisor(5, 1); - - gl.enableVertexAttribArray(6); - gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); - gl.vertexAttribPointer(6, 3, gl.FLOAT, false, 3 * 4, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.vertexAttribDivisor(6, 1); gl.enableVertexAttribArray(6); gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); @@ -234,12 +226,6 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(6, 1); - gl.enableVertexAttribArray(2); - gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); - gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 4, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - gl.vertexAttribDivisor(2, 1); - updateCubes = (startIndex) => { // up2 const newSides = allSides.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index b5652cc26..750e62a86 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -117,22 +117,24 @@ export abstract class WorldRendererCommon this.downloadedTextureImage = this.material.map!.image } // TODO - const loadBlockStates = async () => { - return new Promise(resolve => { - if (this.customBlockStatesData) return resolve(this.customBlockStatesData) - return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { - this.downloadedBlockStatesData = data - // todo - this.renderUpdateEmitter.emit('blockStatesDownloaded') - resolve(data) + setTimeout(() => { + const loadBlockStates = async () => { + return new Promise(resolve => { + if (this.customBlockStatesData) return resolve(this.customBlockStatesData) + return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { + this.downloadedBlockStatesData = data + // todo + this.renderUpdateEmitter.emit('blockStatesDownloaded') + resolve(data) + }) }) - }) - } - loadBlockStates().then((blockStates) => { - for (const worker of this.workers) { - worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: texture.image.width }) } - }) + loadBlockStates().then((blockStates) => { + for (const worker of this.workers) { + worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: texture.image.width }) + } + }) + }, 500) }) } From 33d563cf9488970fc4d4e485ec6a7441f8129d6b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 04:19:22 +0300 Subject: [PATCH 70/86] fix action --- scripts/githubActions.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/githubActions.mjs b/scripts/githubActions.mjs index ba7b8566d..94a64506b 100644 --- a/scripts/githubActions.mjs +++ b/scripts/githubActions.mjs @@ -8,7 +8,7 @@ const fns = { if (!aliasesRaw) throw new Error('No aliases found') const aliases = aliasesRaw.split('\n').map((x) => x.split('=')) const githubActionsPull = process.env.PULL_URL?.split('/').at(-1) - if (!githubActionsPull) throw new Error(`Not a pull request, got ${process.env.GITHUB_REF}`) + if (!githubActionsPull) throw new Error(`Not a pull request, got ${process.env.PULL_URL}`) const prNumber = githubActionsPull[1] const alias = aliases.find((x) => x[0] === prNumber) if (alias) { @@ -18,7 +18,7 @@ const fns = { } } -function setOutput(key, value) { +function setOutput (key, value) { // Temporary hack until core actions library catches up with github new recommendations const output = process.env['GITHUB_OUTPUT'] fs.appendFileSync(output, `${key}=${value}${os.EOL}`) From 324f07014bad84faf5d30e79703fde74e467b49d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 04:21:17 +0300 Subject: [PATCH 71/86] fix playground --- prismarine-viewer/examples/webglRendererWorker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 348d0c4d9..fd703bcbc 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -32,7 +32,7 @@ const updateSize = (width, height) => { export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: ImageBitmapSource, isPlayground: boolean, FragShaderOverride?) => { - isPlayground = false + // isPlayground = false // blockStates = blockStatesJson const textureBitmap = await createImageBitmap(imageBlob) const textureWidth = textureBitmap.width From 0cebd53e3da6d4de920f54b65bf04789beb83f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B5=D0=BB=D0=BE=D0=B2?= Date: Wed, 10 Apr 2024 04:27:09 +0300 Subject: [PATCH 72/86] Triangle Strip --- prismarine-viewer/examples/webglRendererWorker.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index fa36a816c..4c714d49b 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -89,10 +89,10 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im let SideMesh = new Float32Array([ -0.5, -0.5, -0.5, 0.0, 0.0, // Bottom-let 0.5, -0.5, -0.5, 1.0, 0.0, // bottom-right - 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - 0.5, 0.5, -0.5, 1.0, 1.0, // top-right -0.5, 0.5, -0.5, 0.0, 1.0, // top-let - -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let + 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + // 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + // -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let // ront ace ]) @@ -407,7 +407,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im camera.updateMatrix() if (!globalThis.stopRendering) { - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, (isPlayground ? NumberOfCube * 6 : allSides.length)); + gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, (isPlayground ? NumberOfCube * 6 : allSides.length)); } //gl.bindVertexArray(null) From 890afee8e72632d00bb2c56122607845ee591abf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 21:38:10 +0300 Subject: [PATCH 73/86] [pick] creative gamemode for ?singleplayer=1 --- src/optionsStorage.ts | 4 +++- src/react/CreateWorldProvider.tsx | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 8490053cb..de987378c 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -61,7 +61,9 @@ const defaultOptions = { excludeCommunicationDebugEvents: [], preventDevReloadWhilePlaying: false, numWorkers: 4, - localServerOptions: {} as any, + localServerOptions: { + gameMode: 1 + } as any, preferLoadReadonly: false, disableLoadPrompts: false, guestUsername: 'guest', diff --git a/src/react/CreateWorldProvider.tsx b/src/react/CreateWorldProvider.tsx index 58208f955..a25e8bcda 100644 --- a/src/react/CreateWorldProvider.tsx +++ b/src/react/CreateWorldProvider.tsx @@ -51,7 +51,8 @@ export default () => { levelName: title, version, generation, - 'worldFolder': savePath + 'worldFolder': savePath, + gameMode: 0, }, })) }} From bb8a8413aeec10a64e198589ad1077b36381b20b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 10 Apr 2024 22:13:44 +0300 Subject: [PATCH 74/86] implement buffer extend --- .../examples/webglRendererWorker.ts | 84 ++++++++++++------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index f7ab8a901..7112bbb3c 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -7,11 +7,14 @@ import FragShader from './_FragmentShader.frag' import { BlockFaceType, BlockType } from './shared' let allSides = [] as [number, number, number, BlockFaceType][] +let allSidesAdded = 0 +let allSidesUpdated = false + let chunksArrIndexes = {} let freeArrayIndexes = [] as [number, number][] let rendering = true let sidePositions -let updateCubes: (startIndex: any) => void +let updateCubes: (startIndex: any, forceUpdate?) => void let lastNotUpdatedIndex let lastNotUpdatedArrSize let animationTick = 0; @@ -91,14 +94,14 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im 0.5, -0.5, -0.5, 1.0, 0.0, // bottom-right -0.5, 0.5, -0.5, 0.0, 1.0, // top-let 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - // 0.5, 0.5, -0.5, 1.0, 1.0, // top-right - // -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let + // 0.5, 0.5, -0.5, 1.0, 1.0, // top-right + // -0.5, -0.5, -0.5, 0.0, 0.0, // bottom-let // ront ace ]) - let NumberOfCube = isPlayground ? 10_000 : 1_000_000 + let NumberOfCube = isPlayground ? 10_000 : 10_000 sidePositions = new Float32Array(NumberOfCube * 3 * 6) let sideTextureIndices = new Float32Array(NumberOfCube * 1 * 6); @@ -172,19 +175,19 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im let instanceCubeSide = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - gl.bufferData(gl.ARRAY_BUFFER, sidePositions, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sidePositions, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - gl.bufferData(gl.ARRAY_BUFFER, sideTextureIndices, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sideTextureIndices, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); - gl.bufferData(gl.ARRAY_BUFFER, sideBiomeColor, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sideBiomeColor, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); - gl.bufferData(gl.ARRAY_BUFFER, sideIndexes, gl.DYNAMIC_DRAW); // todo + gl.bufferData(gl.ARRAY_BUFFER, sideIndexes, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); VAO = gl.createVertexArray(); let VBO = gl.createBuffer(); @@ -226,7 +229,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.vertexAttribDivisor(6, 1); - updateCubes = (startIndex) => { + updateCubes = (startIndex, forceUpdate) => { // up2 const newSides = allSides.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) newSides.sort((a, b) => { @@ -260,32 +263,40 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // startIndex = 0 // TODO! console.log('startIndex', startIndex, sidePositions.length, allSides.length) - const updateBuffersSize = allSides.length > NumberOfCube - if (updateBuffersSize) { + const prepareBuffersUpdate = allSides.length > NumberOfCube || globalThis.testUpdate + globalThis.testUpdate = false + if (prepareBuffersUpdate) { NumberOfCube += 1_000_000 + updateCubes(0, true) + return } - gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); - if (updateBuffersSize) { - //gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NumberOfCube * 3), gl.STATIC_DRAW); + globalThis.NumberOfCube = NumberOfCube + + const supplyData = (data, step) => { + if (forceUpdate) { + globalThis.updatedBufferSize = NumberOfCube + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NumberOfCube * step), gl.STATIC_DRAW); + } + gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * step, data); // update buffer content + const error = gl.getError() + if (error) throw new Error("SUBDATA ERROR") + gl.bindBuffer(gl.ARRAY_BUFFER, null); } - const XYZ_SIZE = 3 - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * XYZ_SIZE, sidePositions); // update buffer content - gl.bindBuffer(gl.ARRAY_BUFFER, null); + + gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); + supplyData(sidePositions, 3) gl.bindBuffer(gl.ARRAY_BUFFER, instanceTextureID); - if (updateBuffersSize) { - //gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(NumberOfCube), gl.STATIC_DRAW); - } - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4, sideTextureIndices); // update buffer content - gl.bindBuffer(gl.ARRAY_BUFFER, null); + supplyData(sideTextureIndices, 1) gl.bindBuffer(gl.ARRAY_BUFFER, instanceBiomeColor); - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4 * XYZ_SIZE, sideBiomeColor); // update buffer content - gl.bindBuffer(gl.ARRAY_BUFFER, null); + supplyData(sideBiomeColor, 3) gl.bindBuffer(gl.ARRAY_BUFFER, instanceCubeSide); - gl.bufferSubData(gl.ARRAY_BUFFER, startIndex * 4, sideIndexes); // update buffer content - gl.bindBuffer(gl.ARRAY_BUFFER, null); + supplyData(sideIndexes, 1) + + allSidesAdded = allSides.length + allSidesUpdated = false } globalThis.updateCubes = updateCubes @@ -368,7 +379,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im updateSize(gl.canvas.width, gl.canvas.height) const renderLoop = (performance) => { requestAnimationFrame(renderLoop) - if (!rendering) return + if (!rendering && !allSidesUpdated) return // gl.canvas.width = window.innerWidth * window.devicePixelRatio // gl.canvas.height = window.innerHeight * window.devicePixelRatio if (newWidth || newHeight) { @@ -393,9 +404,9 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im camera.updateMatrix() if (!globalThis.stopRendering) { - gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, (isPlayground ? NumberOfCube * 6 : allSides.length)); + allSidesUpdated = true + gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, (isPlayground ? NumberOfCube * 6 : allSidesAdded)); } - //gl.bindVertexArray(null) renderedFrames++ } @@ -531,6 +542,21 @@ onmessage = function (e) { } } +globalThis.testDuplicates = () => { + const duplicates = allSides.filter((value, index, self) => self.indexOf(value) !== index) + console.log('duplicates', duplicates) +} + +globalThis.exportData = () => { + const optimizedData = allSides.map(([x, y, z, side]) => { + return [x, y, z, side.face, side.textureIndex] + }) + const json = JSON.stringify(optimizedData) + const sizeMb = new Blob([json]).size / 1024 / 1024 + console.log('size', sizeMb) + // return json +} + setInterval(() => { if (autoTickUpdate) { animationTick = (animationTick + 1) % autoTickUpdate; From a313c5ffc155f07ff6a9bd560764b6de8caf6db6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Apr 2024 00:52:50 +0300 Subject: [PATCH 75/86] new stats! --- cypress/e2e/shared.ts | 14 ++--- prismarine-viewer/examples/playground.ts | 33 ------------ prismarine-viewer/examples/webglRenderer.ts | 54 +++++++++++++------ .../examples/webglRendererWorker.ts | 21 ++++---- prismarine-viewer/viewer/lib/viewer.ts | 5 ++ .../viewer/lib/worldDataEmitter.ts | 6 +++ .../viewer/lib/worldrendererWebgl.ts | 4 ++ src/index.ts | 2 + src/utils.ts | 2 +- 9 files changed, 74 insertions(+), 67 deletions(-) diff --git a/cypress/e2e/shared.ts b/cypress/e2e/shared.ts index c445b7df6..9292a8d55 100644 --- a/cypress/e2e/shared.ts +++ b/cypress/e2e/shared.ts @@ -1,15 +1,15 @@ import { AppOptions } from '../../src/optionsStorage' export const cleanVisit = (url?) => { - cy.clearLocalStorage() - visit(url) + cy.clearLocalStorage() + visit(url) } export const visit = (url = '/') => { - window.localStorage.cypress = 'true' - cy.visit(url) + window.localStorage.cypress = 'true' + cy.visit(url) } export const setOptions = (options: Partial) => { - cy.window().then(win => { - Object.assign(win['options'], options) - }) + cy.window().then(win => { + Object.assign(win['options'], options) + }) } diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index bbd038a8f..49314cd39 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -226,39 +226,6 @@ async function main () { window['worldView'] = worldView window['viewer'] = viewer - // const jsonData = await fetch('https://bluecolored.de/bluemap/maps/overworld/tiles/0/x-2/2/z1/6.json?584662').then(r => r.json()) - - // const uniforms = { - // distance: { value: 0 }, - // sunlightStrength: { value: 1 }, - // ambientLight: { value: 0 }, - // skyColor: { value: new THREE.Color(0.5, 0.5, 1) }, - // voidColor: { value: new THREE.Color(0, 0, 0) }, - // hiresTileMap: { - // value: { - // map: null, - // size: 100, - // scale: new THREE.Vector2(1, 1), - // translate: new THREE.Vector2(), - // pos: new THREE.Vector2(), - // } - // } - - // } - - // const shader1 = new THREE.ShaderMaterial({ - // uniforms: uniforms, - // vertexShader: [0, 0, 0, 0], - // fragmentShader: fragmentShader, - // transparent: false, - // depthWrite: true, - // depthTest: true, - // vertexColors: true, - // side: THREE.FrontSide, - // wireframe: false - // }) - - //@ts-ignore // const controls = new OrbitControls(viewer.camera, nullRenderer.domElement) // controls.target.set(targetPos.x + 0.5, targetPos.y + 0.5, targetPos.z + 0.5) diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index 66280c70b..e586b73c2 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -1,6 +1,7 @@ import { generateSpiralMatrix } from 'flying-squid/dist/utils' import { Viewer } from '../viewer/lib/viewer' import { options } from '../../src/optionsStorage' +import { addNewStat } from './newStats' let worker @@ -89,7 +90,7 @@ export const initWebglRenderer = async (version: string, postRender = () => { }, // replacable by initWebglRenderer worker = new Worker('./webglRendererWorker.js') - addFpsCounter() + addFpsCounters() sendWorkerMessage({ canvas: offscreen, imageBlob, @@ -150,29 +151,48 @@ export const setAnimationTick = (tick: number, frames?: number) => { }) } +globalThis.exportData = () => { + worker.postMessage({ type: 'exportData' }) + const controller = new AbortController() + worker.addEventListener('message', (e) => { + try { + const a = document.createElement('a') + const dataObj = e.data + const data = JSON.stringify(dataObj) + const objectURL = URL.createObjectURL(new Blob([data], { type: 'application/json' })) + a.href = objectURL + a.download = 'data.json' + a.click() + URL.revokeObjectURL(objectURL) + } finally { + controller.abort() + } + }, { signal: controller.signal }) +} + -const addFpsCounter = () => { - const fpsCounter = document.createElement('div') - fpsCounter.id = 'fps-counter' - fpsCounter.style.position = 'fixed' - fpsCounter.style.top = '0' - fpsCounter.style.right = '0' - // gray bg - fpsCounter.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - fpsCounter.style.color = 'white' - fpsCounter.style.padding = '2px' - fpsCounter.style.fontFamily = 'monospace' - fpsCounter.style.fontSize = '12px' - fpsCounter.style.zIndex = '10000' - document.body.appendChild(fpsCounter) +const addFpsCounters = () => { + const { updateText } = addNewStat('fps') let prevTimeout worker.addEventListener('message', (e) => { if (e.data.type === 'fps') { - fpsCounter.innerText = `FPS: ${e.data.fps}` + updateText(`FPS: ${e.data.fps}`) if (prevTimeout) clearTimeout(prevTimeout); prevTimeout = setTimeout(() => { - fpsCounter.innerText = '' + updateText('') }, 1002) } }) + + const { updateText: updateText2 } = addNewStat('fps-main', 90, 0, 20) + let updates = 0 + const mainLoop = () => { + requestAnimationFrame(mainLoop) + updates++ + } + mainLoop() + setInterval(() => { + updateText2(`Main Loop: ${updates}`) + updates = 0 + }, 1000) } diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 7112bbb3c..b784f65ac 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -8,7 +8,7 @@ import { BlockFaceType, BlockType } from './shared' let allSides = [] as [number, number, number, BlockFaceType][] let allSidesAdded = 0 -let allSidesUpdated = false +let needsSidesUpdate = false let chunksArrIndexes = {} let freeArrayIndexes = [] as [number, number][] @@ -296,7 +296,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im supplyData(sideIndexes, 1) allSidesAdded = allSides.length - allSidesUpdated = false + needsSidesUpdate = true } globalThis.updateCubes = updateCubes @@ -379,7 +379,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im updateSize(gl.canvas.width, gl.canvas.height) const renderLoop = (performance) => { requestAnimationFrame(renderLoop) - if (!rendering && !allSidesUpdated) return + if (!rendering && !needsSidesUpdate) return // gl.canvas.width = window.innerWidth * window.devicePixelRatio // gl.canvas.height = window.innerHeight * window.devicePixelRatio if (newWidth || newHeight) { @@ -404,8 +404,8 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im camera.updateMatrix() if (!globalThis.stopRendering) { - allSidesUpdated = true gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, (isPlayground ? NumberOfCube * 6 : allSidesAdded)); + needsSidesUpdate = false } renderedFrames++ @@ -540,6 +540,9 @@ onmessage = function (e) { if (e.data.type === 'fullReset') { fullReset() } + if (e.data.type === 'exportData') { + postMessage({ type: 'exportData', data: exportData() }) + } } globalThis.testDuplicates = () => { @@ -547,14 +550,14 @@ globalThis.testDuplicates = () => { console.log('duplicates', duplicates) } -globalThis.exportData = () => { +const exportData = () => { const optimizedData = allSides.map(([x, y, z, side]) => { return [x, y, z, side.face, side.textureIndex] }) - const json = JSON.stringify(optimizedData) - const sizeMb = new Blob([json]).size / 1024 / 1024 - console.log('size', sizeMb) - // return json + const json = optimizedData + // const sizeMb = new Blob([json]).size / 1024 / 1024 + // console.log('size', sizeMb) + return json } setInterval(() => { diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 57f5ee5e5..de678b14a 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -9,6 +9,7 @@ import EventEmitter from 'events' import { EffectComposer, RenderPass, ShaderPass, FXAAShader } from 'three-stdlib' import { sendCameraToWorker } from '../../examples/webglRenderer' import { WorldRendererThree } from './worldrendererThree' +import { generateSpiralMatrix } from 'flying-squid/dist/utils' export class Viewer { scene: THREE.Scene @@ -178,6 +179,10 @@ export class Viewer { this.world.updateViewerPosition(pos) }) + emitter.on('renderDistance', (d) => { + this.world.chunksLength = generateSpiralMatrix(d).length + }) + emitter.emit('listening') this.domElement.addEventListener?.('pointerdown', (evt) => { diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index fb17a9942..540b554ad 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -36,6 +36,11 @@ export class WorldDataEmitter extends EventEmitter { }) } + updateViewDistance (viewDistance: number) { + this.viewDistance = viewDistance + this.emitter.emit('renderDistance', viewDistance) + } + listenToBot (bot: typeof __type_bot) { const emitEntity = (e) => { if (!e || e === bot.entity) return @@ -73,6 +78,7 @@ export class WorldDataEmitter extends EventEmitter { return bot.world.getBlock(new Vec3(x, y, z)).entity }, })) + this.emitter.emit('renderDistance', this.viewDistance) }) // node.js stream data event pattern if (this.emitter.listenerCount('blockEntities')) { diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts index 6141648c5..15fa587ce 100644 --- a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -1,3 +1,4 @@ +import { updateStatText } from '../../examples/newStats' import { addBlocksSection, removeBlocksSection, sendWorkerMessage } from '../../examples/webglRenderer' import type { WebglData } from '../prepare/webglData' import { loadJSON } from './utils.web' @@ -7,6 +8,7 @@ export class WorldRendererWebgl extends WorldRendererCommon { newChunks = {} as Record webglData: WebglData stopBlockUpdate = false + chunksLength = 0 constructor(numWorkers = 4) { super(numWorkers) @@ -34,6 +36,8 @@ export class WorldRendererWebgl extends WorldRendererCommon { if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return addBlocksSection(data.key, data.geometry) + const chunkDistance = Math.round(Math.max(Math.abs((chunkCoords[0]) - this.viewerPosition!.x) / 16, Math.abs(chunkCoords[2] - this.viewerPosition!.y) / 16)) + updateStatText('loaded-chunks', Object.keys(this.loadedChunks).length + `/${this.chunksLength} chunks (${chunkDistance})`) // const blocks = Object.values(data.geometry.blocks) as any[] this.newChunks[data.key] = data.geometry } diff --git a/src/index.ts b/src/index.ts index 67fb798da..8310b810b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,6 +97,7 @@ import { possiblyHandleStateVariable } from './googledrive' import flyingSquidEvents from './flyingSquidEvents' import { hideNotification, notificationProxy } from './react/NotificationProvider' import { initWebglRenderer } from 'prismarine-viewer/examples/webglRenderer' +import { addNewStat } from 'prismarine-viewer/examples/newStats' // import { ViewerBase } from 'prismarine-viewer/viewer/lib/viewerWrapper' window.debug = debug @@ -391,6 +392,7 @@ async function connect (connectOptions: { postRenderFrameFn() viewer.update() }) + addNewStat('loaded-chunks') if (singleplayer) { // SINGLEPLAYER EXPLAINER: diff --git a/src/utils.ts b/src/utils.ts index 4b1239d5e..fc73514c7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -173,7 +173,7 @@ export const setRenderDistance = () => { localServer!.players[0].view = 0 renderDistance = 0 } - worldView.viewDistance = renderDistance + worldView.updateViewDistance(renderDistance) prevRenderDistance = renderDistance } export const reloadChunks = async () => { From a3c414e09ef4a3b949066f565f780261d71aeccf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Apr 2024 00:55:25 +0300 Subject: [PATCH 76/86] commit stats --- prismarine-viewer/examples/newStats.ts | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 prismarine-viewer/examples/newStats.ts diff --git a/prismarine-viewer/examples/newStats.ts b/prismarine-viewer/examples/newStats.ts new file mode 100644 index 000000000..23b9b2af3 --- /dev/null +++ b/prismarine-viewer/examples/newStats.ts @@ -0,0 +1,34 @@ +let rightOffset = 0 + +const stats = {} + +export const addNewStat = (id: string, width = 80, x = rightOffset, y = 0) => { + const pane = document.createElement('div') + pane.id = 'fps-counter' + pane.style.position = 'fixed' + pane.style.top = `${y}px` + pane.style.right = `${x}px` + // gray bg + pane.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' + pane.style.color = 'white' + pane.style.padding = '2px' + pane.style.fontFamily = 'monospace' + pane.style.fontSize = '12px' + pane.style.zIndex = '10000' + document.body.appendChild(pane) + stats[id] = pane + if (y === 0) { // otherwise it's a custom position + rightOffset += width + } + + return { + updateText: (text: string) => { + pane.innerText = text + } + } +} + +export const updateStatText = (id, text) => { + if (!stats[id]) return + stats[id].innerText = text +} From 4f9efb4c01f5b8010d4f8379b774a53e5e270cee Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Apr 2024 02:56:41 +0300 Subject: [PATCH 77/86] [wip] fixture export / loading! --- prismarine-viewer/examples/playground.ts | 34 ++++++++- prismarine-viewer/examples/webglRenderer.ts | 39 ++++++++--- .../examples/webglRendererWorker.ts | 69 +++++++++---------- 3 files changed, 94 insertions(+), 48 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index 49314cd39..d1752a3fe 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -14,7 +14,7 @@ import { TWEEN_DURATION } from '../viewer/lib/entities' import Entity from '../viewer/lib/entity/Entity' // import * as Mathgl from 'math.gl' import { findTextureInBlockStates } from '../../src/playerWindows' -import { initWebglRenderer, setAnimationTick } from './webglRenderer' +import { initWebglRenderer, loadFixtureSides, setAnimationTick } from './webglRenderer' import { renderToDom } from '@zardoy/react-util' globalThis.THREE = THREE @@ -73,7 +73,14 @@ async function main () { let continuousRender = false // const { version } = params - const version = '1.20.2' + let fixtureUrl = qs.get('fixture') + let fixture + if (fixtureUrl) { + console.log('Loading fixture') + fixture = await fetch(fixtureUrl).then(r => r.json()) + console.log('Loaded fixture') + } + const version = fixture.version ?? '1.20.2' // temporary solution until web worker is here, cache data for faster reloads const globalMcData = window['mcData'] if (!globalMcData['version']) { @@ -150,7 +157,7 @@ async function main () { viewer.setVersion(version) globalThis.viewer = viewer - await initWebglRenderer(version, () => { }, true) + await initWebglRenderer(version, () => { }, !enableControls && !fixture, true) const simpleControls = () => { let pressedKeys = new Set() const loop = () => { @@ -216,6 +223,27 @@ async function main () { } simpleControls() renderPlayground() + + const writeToIndexedDb = async (name, data) => { + const db = await window.indexedDB.open(name, 1) + db.onupgradeneeded = (e) => { + const db = (e.target as any).result + db.createObjectStore(name) + } + db.onsuccess = (e) => { + const db = (e.target as any).result + const tx = db.transaction(name, 'readwrite') + const store = tx.objectStore(name) + store.add(data, name) + } + } + + if (fixture) { + loadFixtureSides(fixture.sides) + const pos = fixture.camera[0] + viewer.camera.position.set(pos[0], pos[1], pos[2]) + } + if (!enableControls) return // Create viewer diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index e586b73c2..c50cbcef6 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -47,6 +47,12 @@ export const addBlocksSection = (key, data) => { }) } +export const loadFixtureSides = (json) => { + sendWorkerMessage({ + type: 'loadFixture', json + }) +} + export const sendCameraToWorker = () => { const cameraData = ['rotation', 'position'].reduce((acc, key) => { acc[key] = ['x', 'y', 'z'].reduce((acc2, key2) => { @@ -68,8 +74,8 @@ export const removeBlocksSection = (key) => { } let playground = false -export const initWebglRenderer = async (version: string, postRender = () => { }, isPlayground = false) => { - playground = isPlayground +export const initWebglRenderer = async (version: string, postRender = () => { }, playgroundModeInWorker = false, actuallyPlayground = false) => { + playground = playgroundModeInWorker viewer.setVersion(version) await new Promise(resolve => { // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) @@ -94,7 +100,7 @@ export const initWebglRenderer = async (version: string, postRender = () => { }, sendWorkerMessage({ canvas: offscreen, imageBlob, - isPlayground, + isPlayground: playgroundModeInWorker, FragShaderOverride: localStorage.FragShader }, [offscreen]) @@ -129,7 +135,7 @@ export const initWebglRenderer = async (version: string, postRender = () => { }, } postRender() // TODO! do it in viewer to avoid possible delays - if (playground && ['rotation', 'position'].some((key) => oldCamera[key] !== viewer.camera[key])) { + if (actuallyPlayground && ['rotation', 'position'].some((key) => oldCamera[key] !== viewer.camera[key])) { // TODO fix for (const [key, val] of Object.entries(oldCamera)) { for (const key2 of Object.keys(val)) { @@ -151,17 +157,30 @@ export const setAnimationTick = (tick: number, frames?: number) => { }) } -globalThis.exportData = () => { +globalThis.exportFixture = () => { worker.postMessage({ type: 'exportData' }) const controller = new AbortController() - worker.addEventListener('message', (e) => { + worker.addEventListener('message', async (e) => { + const receivedData = e.data.data; + console.log('received fixture') + // await new Promise(resolve => { + // setTimeout(resolve, 0) + // }) try { const a = document.createElement('a') - const dataObj = e.data - const data = JSON.stringify(dataObj) - const objectURL = URL.createObjectURL(new Blob([data], { type: 'application/json' })) + type Vec3 = [number, number, number] + type PlayTimeline = [pos: Vec3, rot: Vec3, time: number] + const vec3ToArr = (vec3: { x, y, z }) => [vec3.x, vec3.y, vec3.z] as Vec3 + // const dataObj = { + // ...receivedData, + // version: viewer.version, + // camera: [vec3ToArr(viewer.camera.position), vec3ToArr(viewer.camera.rotation)], + // playTimeline: [] as PlayTimeline[] + // } + // split into two chunks + const objectURL = URL.createObjectURL(new Blob([receivedData.sides.buffer], { type: 'application/octet-stream' })) a.href = objectURL - a.download = 'data.json' + a.download = 'fixture.bin' a.click() URL.revokeObjectURL(objectURL) } finally { diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index b784f65ac..daf19b23d 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -19,6 +19,14 @@ let lastNotUpdatedIndex let lastNotUpdatedArrSize let animationTick = 0; +const updateCubesWhenAvailable = (pos) => { + if (updateCubes) { + updateCubes(pos) + } else { + setTimeout(updateCubesWhenAvailable, 100) + } +} + const camera = new THREE.PerspectiveCamera(75, 1 / 1, 0.1, 1000) let renderedFrames = 0 @@ -101,7 +109,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im - let NumberOfCube = isPlayground ? 10_000 : 10_000 + let NumberOfCube = isPlayground ? 10_000 : 16_000_000 sidePositions = new Float32Array(NumberOfCube * 3 * 6) let sideTextureIndices = new Float32Array(NumberOfCube * 1 * 6); @@ -137,35 +145,9 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im //sideTextureIndices[i / 3 + j - 1] = 1; } - // sidePositions[i +3] = sidePositions[i] - // sidePositions[i + 4] = sidePositions[i + 2] - // sidePositions[i + 5] = sidePositions[i + 1] - - // sideBiomeColor[i] = (Math.random() ) ; - // sideBiomeColor[i + 1] = (Math.random() ) ; - // sideBiomeColor[i + 2] = (Math.random() ) ; - - // sideIndexes[i/6] = Math.floor(Math.random() * 6); - // sideTextureIndices[i/6] = Math.floor(Math.random() * 800); - // cubeTextureIndices[i / 3] = Math.floor(Math.random() * 800); - // cubeTextureIndices[i / 3] = 0; } - // for (let i = 0; i < NumberOfCube * 6; i += 6) { - // sideTextureIndices[i + 0] = Math.floor(Math.random() * 800); - // sideTextureIndices[i + 1] = Math.floor(Math.random() * 800); - // sideTextureIndices[i + 2] = Math.floor(Math.random() * 800); - // sideTextureIndices[i + 3] = Math.floor(Math.random() * 800); - // sideTextureIndices[i + 4] = Math.floor(Math.random() * 800); - // sideTextureIndices[i + 5] = Math.floor(Math.random() * 800); - // // cubeTextureIndices[i / 3] = 0; - // } - - } - // cubePositions[0] = 0; - // cubePositions[1] = 0; - // cubePositions[2] = 0; let VAO = gl.createVertexArray(); @@ -541,7 +523,18 @@ onmessage = function (e) { fullReset() } if (e.data.type === 'exportData') { - postMessage({ type: 'exportData', data: exportData() }) + const exported = exportData(); + postMessage({ type: 'exportData', data: exported }, undefined, [exported.sides.buffer]) + } + if (e.data.type === 'loadFixture') { + // allSides = e.data.json.map(([x, y, z, face, textureIndex]) => { + // return [x, y, z, { face, textureIndex }] as [number, number, number, BlockFaceType] + // }) + const dataSize = e.data.json.length / 5 + for (let i = 0; i < e.data.json.length; i += 5) { + allSides.push([e.data.json[i], e.data.json[i + 1], e.data.json[i + 2], { face: e.data.json[i + 3], textureIndex: e.data.json[i + 4] }]) + } + updateCubesWhenAvailable(0) } } @@ -551,13 +544,19 @@ globalThis.testDuplicates = () => { } const exportData = () => { - const optimizedData = allSides.map(([x, y, z, side]) => { - return [x, y, z, side.face, side.textureIndex] - }) - const json = optimizedData - // const sizeMb = new Blob([json]).size / 1024 / 1024 - // console.log('size', sizeMb) - return json + // Calculate the total length of the final array + const totalLength = allSides.length * 5; + + // Create a new Int16Array with the total length + const flatData = new Int16Array(totalLength); + + // Fill the flatData array + for (let i = 0; i < allSides.length; i++) { + const [x, y, z, side] = allSides[i]; + flatData.set([x, y, z, side.face, side.textureIndex], i * 5); + } + + return { sides: flatData }; } setInterval(() => { From 830424eec28448eefea0157fdd1ca18e9aefc758 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Apr 2024 19:57:55 +0300 Subject: [PATCH 78/86] [to pick] customize gamemode from menu! --- prismarine-viewer/examples/newStats.ts | 1 + src/react/CreateWorld.tsx | 14 +++++++++++--- src/react/CreateWorldProvider.tsx | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/prismarine-viewer/examples/newStats.ts b/prismarine-viewer/examples/newStats.ts index 23b9b2af3..fb363484f 100644 --- a/prismarine-viewer/examples/newStats.ts +++ b/prismarine-viewer/examples/newStats.ts @@ -15,6 +15,7 @@ export const addNewStat = (id: string, width = 80, x = rightOffset, y = 0) => { pane.style.fontFamily = 'monospace' pane.style.fontSize = '12px' pane.style.zIndex = '10000' + pane.style.pointerEvents = 'none' document.body.appendChild(pane) stats[id] = pane if (y === 0) { // otherwise it's a custom position diff --git a/src/react/CreateWorld.tsx b/src/react/CreateWorld.tsx index ceed77ec5..87b367774 100644 --- a/src/react/CreateWorld.tsx +++ b/src/react/CreateWorld.tsx @@ -8,17 +8,19 @@ import styles from './createWorld.module.css' // const worldTypes = ['default', 'flat', 'largeBiomes', 'amplified', 'customized', 'buffet', 'debug_all_block_states'] const worldTypes = ['default', 'flat'/* , 'void' */] +const gameModes = ['survival', 'creative'/* , 'adventure', 'spectator' */] export const creatingWorldState = proxy({ title: '', type: worldTypes[0], + gameMode: gameModes[0], version: '' }) export default ({ cancelClick, createClick, customizeClick, versions, defaultVersion }) => { const [quota, setQuota] = useState('') - const { title, type, version } = useSnapshot(creatingWorldState) + const { title, type, version, gameMode } = useSnapshot(creatingWorldState) useEffect(() => { creatingWorldState.version = defaultVersion void navigator.storage?.estimate?.().then(({ quota, usage }) => { @@ -54,9 +56,15 @@ export default ({ cancelClick, createClick, customizeClick, versions, defaultVer - + {/* */} +
Default and other world types are WIP
diff --git a/src/react/CreateWorldProvider.tsx b/src/react/CreateWorldProvider.tsx index a25e8bcda..332a1d31c 100644 --- a/src/react/CreateWorldProvider.tsx +++ b/src/react/CreateWorldProvider.tsx @@ -23,7 +23,7 @@ export default () => { }} createClick={async () => { // create new world - const { title, type, version } = creatingWorldState + const { title, type, version, gameMode } = creatingWorldState // todo display path in ui + disable if exist const savePath = await uniqueFileNameFromWorldName(title, getWorldsPath()) await mkdirRecursive(savePath) @@ -52,7 +52,7 @@ export default () => { version, generation, 'worldFolder': savePath, - gameMode: 0, + gameMode: gameMode === 'survival' ? 0 : 1, }, })) }} From a12ab1cd169901492f29bff82c0d63e65290bece Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Apr 2024 20:15:28 +0300 Subject: [PATCH 79/86] clean fix for image.width is undefined! --- prismarine-viewer/examples/webglRenderer.ts | 1 - prismarine-viewer/viewer/lib/utils.web.js | 6 ++- .../viewer/lib/worldrendererCommon.ts | 39 +++++++++---------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index c50cbcef6..b3811b36b 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -76,7 +76,6 @@ export const removeBlocksSection = (key) => { let playground = false export const initWebglRenderer = async (version: string, postRender = () => { }, playgroundModeInWorker = false, actuallyPlayground = false) => { playground = playgroundModeInWorker - viewer.setVersion(version) await new Promise(resolve => { // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) // viewer.world.material.map!.image.onload = () => { diff --git a/prismarine-viewer/viewer/lib/utils.web.js b/prismarine-viewer/viewer/lib/utils.web.js index 72d715b90..cbb942225 100644 --- a/prismarine-viewer/viewer/lib/utils.web.js +++ b/prismarine-viewer/viewer/lib/utils.web.js @@ -2,9 +2,11 @@ const THREE = require('three') const textureCache = {} -function loadTexture (texture, cb) { +function loadTexture (texture, cb, onLoad) { if (!textureCache[texture]) { - textureCache[texture] = new THREE.TextureLoader().load(texture) + textureCache[texture] = new THREE.TextureLoader().load(texture, onLoad) + } else { + onLoad?.() } cb(textureCache[texture]) } diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 750e62a86..05a4c801b 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -1,6 +1,7 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' -import { loadTexture, loadJSON } from './utils' +import { loadJSON } from './utils' +import { loadTexture } from './utils.web' import { EventEmitter } from 'events' import mcDataRaw from 'minecraft-data/data.js'; // handled correctly in esbuild plugin import { dynamicMcDataFiles } from '../../buildWorkerConfig.mjs' @@ -113,28 +114,24 @@ export abstract class WorldRendererCommon texture.minFilter = THREE.NearestFilter texture.flipY = false this.material.map = texture - this.material.map.onUpdate = () => { - this.downloadedTextureImage = this.material.map!.image - } - // TODO - setTimeout(() => { - const loadBlockStates = async () => { - return new Promise(resolve => { - if (this.customBlockStatesData) return resolve(this.customBlockStatesData) - return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { - this.downloadedBlockStatesData = data - // todo - this.renderUpdateEmitter.emit('blockStatesDownloaded') - resolve(data) - }) + }, (tex) => { + this.downloadedTextureImage = this.material.map!.image + const loadBlockStates = async () => { + return new Promise(resolve => { + if (this.customBlockStatesData) return resolve(this.customBlockStatesData) + return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { + this.downloadedBlockStatesData = data + // todo + this.renderUpdateEmitter.emit('blockStatesDownloaded') + resolve(data) }) - } - loadBlockStates().then((blockStates) => { - for (const worker of this.workers) { - worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: texture.image.width }) - } }) - }, 500) + } + loadBlockStates().then((blockStates) => { + for (const worker of this.workers) { + worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: tex.image.width }) + } + }) }) } From 662c823c23bffbaeb1f91ca68f5fb3a692836d8e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Apr 2024 20:19:32 +0300 Subject: [PATCH 80/86] [pick] fix alias set --- scripts/githubActions.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/githubActions.mjs b/scripts/githubActions.mjs index 94a64506b..ab786ea94 100644 --- a/scripts/githubActions.mjs +++ b/scripts/githubActions.mjs @@ -6,10 +6,10 @@ const fns = { async getAlias () { const aliasesRaw = process.env.ALIASES if (!aliasesRaw) throw new Error('No aliases found') - const aliases = aliasesRaw.split('\n').map((x) => x.split('=')) + const aliases = aliasesRaw.split('\n').map((x) => x.trim().split('=')) const githubActionsPull = process.env.PULL_URL?.split('/').at(-1) if (!githubActionsPull) throw new Error(`Not a pull request, got ${process.env.PULL_URL}`) - const prNumber = githubActionsPull[1] + const prNumber = githubActionsPull const alias = aliases.find((x) => x[0] === prNumber) if (alias) { // set github output From e9ca5cf3d2205eb3807f1471366930890810ba8f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 12 Apr 2024 19:28:36 +0300 Subject: [PATCH 81/86] fix playground and slow cam move in worker --- prismarine-viewer/examples/playground.ts | 4 ++-- prismarine-viewer/examples/webglRendererWorker.ts | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/prismarine-viewer/examples/playground.ts b/prismarine-viewer/examples/playground.ts index d1752a3fe..a5c9f6720 100644 --- a/prismarine-viewer/examples/playground.ts +++ b/prismarine-viewer/examples/playground.ts @@ -74,13 +74,13 @@ async function main () { // const { version } = params let fixtureUrl = qs.get('fixture') - let fixture + let fixture: undefined | Record if (fixtureUrl) { console.log('Loading fixture') fixture = await fetch(fixtureUrl).then(r => r.json()) console.log('Loaded fixture') } - const version = fixture.version ?? '1.20.2' + const version = fixture?.version ?? '1.20.2' // temporary solution until web worker is here, cache data for faster reloads const globalMcData = window['mcData'] if (!globalMcData['version']) { diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index daf19b23d..5f5c0161d 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -5,6 +5,7 @@ import VertShader from './_VertexShader.vert' //@ts-ignore import FragShader from './_FragmentShader.frag' import { BlockFaceType, BlockType } from './shared' +import * as tweenJs from '@tweenjs/tween.js' let allSides = [] as [number, number, number, BlockFaceType][] let allSidesAdded = 0 @@ -380,11 +381,12 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im gl.clear(gl.DEPTH_BUFFER_BIT) + tweenJs.update() + camera.updateMatrix() gl.uniformMatrix4fv(ViewUniform, false, camera.matrix.invert().elements); gl.uniformMatrix4fv(ProjectionUniform, false, camera.projectionMatrix.elements); gl.uniform1i(TickUniform, animationTick); - camera.updateMatrix() if (!globalThis.stopRendering) { gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, (isPlayground ? NumberOfCube * 6 : allSidesAdded)); needsSidesUpdate = false @@ -504,7 +506,8 @@ onmessage = function (e) { } if (e.data.type === 'camera') { camera.rotation.set(e.data.camera.rotation.x, e.data.camera.rotation.y, e.data.camera.rotation.z, 'ZYX') - camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) + // camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) + new tweenJs.Tween(camera.position).to({ x: e.data.camera.position.x, y: e.data.camera.position.y, z: e.data.camera.position.z }, 300).start() // 50 } if (e.data.type === 'animationTick') { if (e.data.frames <= 0) { From 4e1257cb7c2ee7d6aab1c3e68e4429ebc98583dc Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 12 Apr 2024 19:33:03 +0300 Subject: [PATCH 82/86] fix cam fixtup --- prismarine-viewer/viewer/lib/viewer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index de678b14a..5d2f0921f 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -31,7 +31,7 @@ export class Viewer { fxaaPass: ShaderPass renderPass: RenderPass - constructor(public renderer: THREE.WebGLRenderer, numWorkers?: number, public enableFXAA = false) { + constructor (public renderer: THREE.WebGLRenderer, numWorkers?: number, public enableFXAA = false) { // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 THREE.ColorManagement.enabled = false renderer.outputColorSpace = THREE.LinearSRGBColorSpace @@ -115,7 +115,7 @@ export class Viewer { if (pos) { let y = pos.y + this.playerHeight if (this.isSneaking) y -= 0.3 - new tweenJs.Tween(cam.position).to({ x: pos.x, y, z: pos.z }, 50).start() + // new tweenJs.Tween(cam.position).to({ x: pos.x, y, z: pos.z }, 50).start() } cam.rotation.set(pitch, yaw, roll, 'ZYX') sendCameraToWorker() @@ -197,7 +197,7 @@ export class Viewer { } update () { - tweenJs.update() + // tweenJs.update() } render () { From 6c5fdbdc5e5da054a4cb1a8f4baa4969e8ab2a56 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 12 Apr 2024 19:38:12 +0300 Subject: [PATCH 83/86] another fixup for cam pos --- prismarine-viewer/viewer/lib/viewer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 5d2f0921f..a01bd1c32 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -116,6 +116,7 @@ export class Viewer { let y = pos.y + this.playerHeight if (this.isSneaking) y -= 0.3 // new tweenJs.Tween(cam.position).to({ x: pos.x, y, z: pos.z }, 50).start() + cam.position.set(pos.x, y, pos.z) } cam.rotation.set(pitch, yaw, roll, 'ZYX') sendCameraToWorker() From 3c4996f1ef762e8fbc98d2ffa28ce3e41ef89d61 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 14 Apr 2024 18:37:12 +0300 Subject: [PATCH 84/86] impl removal, fix cam --- .../examples/webglRendererWorker.ts | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 5f5c0161d..9f59ba59b 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -7,7 +7,7 @@ import FragShader from './_FragmentShader.frag' import { BlockFaceType, BlockType } from './shared' import * as tweenJs from '@tweenjs/tween.js' -let allSides = [] as [number, number, number, BlockFaceType][] +let allSides = [] as ([number, number, number, BlockFaceType] | undefined)[] let allSidesAdded = 0 let needsSidesUpdate = false @@ -216,6 +216,7 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im // up2 const newSides = allSides.slice(startIndex, lastNotUpdatedArrSize ? startIndex + lastNotUpdatedArrSize : undefined) newSides.sort((a, b) => { + if (!a || !b) return 0 const getScore = (b: BlockFaceType) => b.isTransparent ? 1 : 0 return getScore(a[3]) - getScore(b[3]) }) @@ -225,10 +226,12 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im sideIndexes = new Float32Array(newSides.length * 1); sideBiomeColor = new Float32Array(newSides.length * 3); for (let i = 0; i < newSides.length * 3; i += 3) { - sidePositions[i] = newSides[i / 3][0] - sidePositions[i + 1] = newSides[i / 3][1] - sidePositions[i + 2] = newSides[i / 3][2] - const block = newSides[i / 3][3] as BlockFaceType + const newSide = newSides[i / 3]; + if (!newSide) continue + sidePositions[i] = newSide[0] + sidePositions[i + 1] = newSide[1] + sidePositions[i + 2] = newSide[2] + const block = newSide[3] as BlockFaceType if (block.tint) { const [r, g, b] = block.tint sideBiomeColor[i] = r @@ -280,9 +283,18 @@ export const initWebglRenderer = async (canvas: HTMLCanvasElement, imageBlob: Im allSidesAdded = allSides.length needsSidesUpdate = true + lastNotUpdatedArrSize = undefined } globalThis.updateCubes = updateCubes + globalThis.resetHalfScene = () => { + for (let i = 0; i < allSides.length / 2; i++) { + allSides[i] = undefined + } + lastNotUpdatedIndex = 0 + lastNotUpdatedArrSize = allSides.length / 2 + updateCubes(0) + } const cleanupFirstChunks = () => { allSides = [] gl.bindBuffer(gl.ARRAY_BUFFER, instanceVBO); @@ -489,12 +501,19 @@ onmessage = function (e) { // updateCubes?.(currentLength) } if (e.data.type === 'addBlocksSectionDone') { - updateCubes?.(lastNotUpdatedIndex) + updateCubesWhenAvailable(lastNotUpdatedIndex) lastNotUpdatedIndex = undefined lastNotUpdatedArrSize = undefined } if (e.data.type === 'removeBlocksSection') { - // const [startIndex, endIndex] = chunksArrIndexes[e.data.key] + // fill data with 0 + const [startIndex, endIndex] = chunksArrIndexes[e.data.key] + for (let i = startIndex; i < endIndex; i++) { + allSides[i] = undefined + } + lastNotUpdatedArrSize = endIndex - startIndex + updateCubes(startIndex) + // freeArrayIndexes.push([startIndex, endIndex]) // // merge freeArrayIndexes TODO @@ -507,7 +526,12 @@ onmessage = function (e) { if (e.data.type === 'camera') { camera.rotation.set(e.data.camera.rotation.x, e.data.camera.rotation.y, e.data.camera.rotation.z, 'ZYX') // camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) - new tweenJs.Tween(camera.position).to({ x: e.data.camera.position.x, y: e.data.camera.position.y, z: e.data.camera.position.z }, 300).start() // 50 + if (camera.position.x === 0 && camera.position.y === 0 && camera.position.z === 0) { + // initial camera position + camera.position.set(e.data.camera.position.x, e.data.camera.position.y, e.data.camera.position.z) + } else { + new tweenJs.Tween(camera.position).to({ x: e.data.camera.position.x, y: e.data.camera.position.y, z: e.data.camera.position.z }, 50).start() + } } if (e.data.type === 'animationTick') { if (e.data.frames <= 0) { From 137666c65c6da9cb6db6eb6767fb7d76d9fe3d34 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 14 Apr 2024 18:43:19 +0300 Subject: [PATCH 85/86] FIX waitForChunksToRender ! fix a lot of worker issues! implement allChunksLoaded method correctly!! --- prismarine-viewer/examples/webglRenderer.ts | 21 ++++--- prismarine-viewer/viewer/lib/models.ts | 2 +- prismarine-viewer/viewer/lib/viewer.ts | 3 +- prismarine-viewer/viewer/lib/worker.js | 54 ++++++++++------- prismarine-viewer/viewer/lib/world.ts | 1 + .../viewer/lib/worldrendererCommon.ts | 60 +++++++++++++++---- .../viewer/lib/worldrendererThree.ts | 1 + .../viewer/lib/worldrendererWebgl.ts | 27 +++++++-- 8 files changed, 119 insertions(+), 50 deletions(-) diff --git a/prismarine-viewer/examples/webglRenderer.ts b/prismarine-viewer/examples/webglRenderer.ts index b3811b36b..1047a50f2 100644 --- a/prismarine-viewer/examples/webglRenderer.ts +++ b/prismarine-viewer/examples/webglRenderer.ts @@ -35,16 +35,15 @@ export const addBlocksSection = (key, data) => { sendWorkerMessage({ type: 'addBlocksSection', data, key }) - if (isWaitingToUpload) return - isWaitingToUpload = true - viewer.waitForChunksToRender().then(() => { - isWaitingToUpload = false - if (allReceived || (true && Object.values(viewer.world.newChunks).length)) { - sendWorkerMessage({ - type: 'addBlocksSectionDone' - }) - } - }) + if (playground && !isWaitingToUpload) { + isWaitingToUpload = true + // viewer.waitForChunksToRender().then(() => { + // isWaitingToUpload = false + // sendWorkerMessage({ + // type: 'addBlocksSectionDone' + // }) + // }) + } } export const loadFixtureSides = (json) => { @@ -75,7 +74,7 @@ export const removeBlocksSection = (key) => { let playground = false export const initWebglRenderer = async (version: string, postRender = () => { }, playgroundModeInWorker = false, actuallyPlayground = false) => { - playground = playgroundModeInWorker + playground = actuallyPlayground await new Promise(resolve => { // console.log('viewer.world.material.map!.image', viewer.world.material.map!.image) // viewer.world.material.map!.image.onload = () => { diff --git a/prismarine-viewer/viewer/lib/models.ts b/prismarine-viewer/viewer/lib/models.ts index b5ef1871c..4c82b030e 100644 --- a/prismarine-viewer/viewer/lib/models.ts +++ b/prismarine-viewer/viewer/lib/models.ts @@ -811,7 +811,7 @@ function getModelVariants (block: import('prismarine-block').Block) { return [] } -export const setBlockStatesAndData = (_blockStates: BlockStatesOutput | null, _textureSize: number) => { +export const setRendererData = (_blockStates: BlockStatesOutput | null, _textureSize: number) => { textureSize = _textureSize blockStates = _blockStates! } diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index a01bd1c32..01864cb3c 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -181,7 +181,8 @@ export class Viewer { }) emitter.on('renderDistance', (d) => { - this.world.chunksLength = generateSpiralMatrix(d).length + this.world.viewDistance = d + this.world.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length }) emitter.emit('listening') diff --git a/prismarine-viewer/viewer/lib/worker.js b/prismarine-viewer/viewer/lib/worker.js index 5bf7dabc2..bcc2b06ca 100644 --- a/prismarine-viewer/viewer/lib/worker.js +++ b/prismarine-viewer/viewer/lib/worker.js @@ -1,3 +1,4 @@ +//@ts-check /* global postMessage self */ if (!global.self) { @@ -12,10 +13,11 @@ if (!global.self) { const { Vec3 } = require('vec3') const { World } = require('./world') -const { getSectionGeometry, setBlockStatesAndData } = require('./models') +const { getSectionGeometry, setRendererData } = require('./models') let world = null -let dirtySections = {} +/** @type {Map} */ +let dirtySections = new Map() let blockStatesReady = false function sectionKey (x, y, z) { @@ -26,24 +28,31 @@ function setSectionDirty (pos, value = true) { const x = Math.floor(pos.x / 16) * 16 const y = Math.floor(pos.y / 16) * 16 const z = Math.floor(pos.z / 16) * 16 - const chunk = world.getColumn(x, z) const key = sectionKey(x, y, z) if (!value) { - delete dirtySections[key] + dirtySections.delete(key) postMessage({ type: 'sectionFinished', key }) - } else if (chunk?.getSection(pos)) { - dirtySections[key] = value + return + } + + const chunk = world.getColumn(x, z); + if (chunk?.getSection(pos)) { + dirtySections.set(key, (dirtySections.get(key) || 0) + 1) } else { postMessage({ type: 'sectionFinished', key }) } } self.onmessage = ({ data }) => { + /** @type {any} */ + const globalVar = globalThis; + if (data.type === 'mcData') { - globalThis.mcData = data.mcData + globalVar.mcData = data.mcData world = new World(data.version) - } else if (data.type === 'blockStates') { - setBlockStatesAndData(data.json, data.textureSize) + } else if (data.type === 'rendererData') { + setRendererData(data.json, data.textureSize) + world.outputFormat = data.outputFormat ?? world.outputFormat blockStatesReady = true } else if (data.type === 'dirty') { const loc = new Vec3(data.x, data.y, data.z) @@ -57,35 +66,38 @@ self.onmessage = ({ data }) => { world.setBlockStateId(loc, data.stateId) } else if (data.type === 'reset') { world = null - blocksStates = null - dirtySections = {} + // blocksStates = null + dirtySections = new Map() // todo also remove cached - globalThis.mcData = null + globalVar.mcData = null blockStatesReady = false } } setInterval(() => { if (world === null || !blockStatesReady) return - const sections = Object.keys(dirtySections) - if (sections.length === 0) return + if (dirtySections.size === 0) return // console.log(sections.length + ' dirty sections') // const start = performance.now() - for (const key of sections) { - let [x, y, z] = key.split(',') - x = parseInt(x, 10) - y = parseInt(y, 10) - z = parseInt(z, 10) + for (const key of dirtySections.keys()) { + let [x, y, z] = key.split(',').map(v => parseInt(v, 10)) const chunk = world.getColumn(x, z) if (chunk?.getSection(new Vec3(x, y, z))) { - delete dirtySections[key] const geometry = getSectionGeometry(x, y, z, world) const transferable = [geometry.positions.buffer, geometry.normals.buffer, geometry.colors.buffer, geometry.uvs.buffer] + //@ts-ignore postMessage({ type: 'geometry', key, geometry }, transferable) + } else { + console.info('[mesher] Missing section', x, y, z) } - postMessage({ type: 'sectionFinished', key }) + const dirtyTimes = dirtySections.get(key); + if (!dirtyTimes) throw new Error('dirtySections.get(key) is falsy') + for (let i = 0; i < dirtyTimes; i++) { + postMessage({ type: 'sectionFinished', key }) + } + dirtySections.delete(key) } // const time = performance.now() - start // console.log(`Processed ${sections.length} sections in ${time} ms (${time / sections.length} ms/section)`) diff --git a/prismarine-viewer/viewer/lib/world.ts b/prismarine-viewer/viewer/lib/world.ts index d0075dce0..9aa5311a4 100644 --- a/prismarine-viewer/viewer/lib/world.ts +++ b/prismarine-viewer/viewer/lib/world.ts @@ -31,6 +31,7 @@ export type WorldBlock = Block & { } export class World { + outputFormat = 'threeJs' as 'threeJs' | 'webgl' Chunk: any/* import('prismarine-chunk/types/index').PCChunk */ columns = {} blockCache = {} diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 05a4c801b..ffa764121 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -6,6 +6,7 @@ import { EventEmitter } from 'events' import mcDataRaw from 'minecraft-data/data.js'; // handled correctly in esbuild plugin import { dynamicMcDataFiles } from '../../buildWorkerConfig.mjs' import { toMajor } from './version.js' +import { chunkPos } from './simpleUtils' function mod (x, n) { return ((x % n) + n) % n @@ -20,7 +21,8 @@ export abstract class WorldRendererCommon active = false version = undefined as string | undefined loadedChunks = {} as Record - sectionsOutstanding = new Set() + finishedChunks = {} as Record + sectionsOutstanding = new Map() renderUpdateEmitter = new EventEmitter() customBlockStatesData = undefined as any customTexturesDataUrl = undefined as string | undefined @@ -33,9 +35,13 @@ export abstract class WorldRendererCommon initialChunksLoad = true enableChunksLoadDelay = false texturesVersion?: string + viewDistance = -1 + chunksLength = 0 // promisesQueue = [] as Promise[] - constructor(numWorkers: number) { + abstract outputFormat: 'threeJs' | 'webgl' + + constructor (numWorkers: number) { // init workers for (let i = 0; i < numWorkers; i++) { // Node environment needs an absolute path, but browser needs the url of the file @@ -51,7 +57,27 @@ export abstract class WorldRendererCommon setTimeout(resolve, 0) }) if (data.type === 'sectionFinished') { - this.sectionsOutstanding.delete(data.key) + if (!this.sectionsOutstanding.get(data.key)) throw new Error(`sectionFinished event for non-outstanding section ${data.key}`) + this.sectionsOutstanding.set(data.key, this.sectionsOutstanding.get(data.key)! - 1) + if (this.sectionsOutstanding.get(data.key) === 0) this.sectionsOutstanding.delete(data.key) + + const chunkCoords = data.key.split(',').map(Number) + if (this.loadedChunks[`${chunkCoords[0]},${chunkCoords[2]}`]) { // ensure chunk data was added, not a neighbor chunk update + const loadingKeys = [...this.sectionsOutstanding.keys()] + if (!loadingKeys.some(key => { + const [x, y, z] = key.split(',').map(Number) + return x === chunkCoords[0] && z === chunkCoords[2] + })) { + this.finishedChunks[`${chunkCoords[0]},${chunkCoords[2]}`] = true + } + } + if (this.sectionsOutstanding.size === 0) { + const allFinished = Object.keys(this.finishedChunks).length === this.chunksLength + if (allFinished) { + this.allChunksLoaded?.() + } + } + this.renderUpdateEmitter.emit('update') } } @@ -65,13 +91,15 @@ export abstract class WorldRendererCommon /** * Optionally update data that are depedendent on the viewer position */ - abstract updatePosDataChunk (key: string): void + updatePosDataChunk?(key: string): void + + allChunksLoaded?(): void updateViewerPosition (pos: Vec3) { this.viewerPosition = pos for (const [key, value] of Object.entries(this.loadedChunks)) { if (!value) continue - this.updatePosDataChunk(key) + this.updatePosDataChunk?.(key) } } @@ -81,12 +109,19 @@ export abstract class WorldRendererCommon } } + getDistance (posAbsolute: Vec3) { + const [botX, botZ] = chunkPos(this.viewerPosition!) + const dx = Math.abs(botX - Math.floor(posAbsolute.x / 16)) + const dz = Math.abs(botZ - Math.floor(posAbsolute.z / 16)) + return [dx, dz] as [number, number] + } + abstract updateShowChunksBorder (value: boolean): void resetWorld () { this.active = false this.loadedChunks = {} - this.sectionsOutstanding = new Set() + this.sectionsOutstanding = new Map() for (const worker of this.workers) { worker.postMessage({ type: 'reset' }) } @@ -129,7 +164,7 @@ export abstract class WorldRendererCommon } loadBlockStates().then((blockStates) => { for (const worker of this.workers) { - worker.postMessage({ type: 'blockStates', json: blockStates, textureSize: tex.image.width }) + worker.postMessage({ type: 'rendererData', json: blockStates, textureSize: tex.image.width, outputFormat: this.outputFormat }) } }) }) @@ -159,7 +194,7 @@ export abstract class WorldRendererCommon } } - setBlockStateId (pos, stateId) { + setBlockStateId (pos: Vec3, stateId: number) { for (const worker of this.workers) { worker.postMessage({ type: 'blockUpdate', pos, stateId }) } @@ -172,14 +207,19 @@ export abstract class WorldRendererCommon if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16)) } - setSectionDirty (pos, value = true) { + setSectionDirty (pos: Vec3, value = true) { + if (this.viewDistance === -1) throw new Error('viewDistance not set') + const distance = this.getDistance(pos) + if (distance[0] > this.viewDistance || distance[1] > this.viewDistance) return + const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`; + // if (this.sectionsOutstanding.has(key)) return this.renderUpdateEmitter.emit('dirty', pos, value) // Dispatch sections to workers based on position // This guarantees uniformity accross workers and that a given section // is always dispatched to the same worker const hash = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), this.workers.length) + this.sectionsOutstanding.set(key, (this.sectionsOutstanding.get(key) ?? 0) + 1) this.workers[hash].postMessage({ type: 'dirty', x: pos.x, y: pos.y, z: pos.z, value }) - this.sectionsOutstanding.add(`${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`) } // Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 553c2360a..5c17153f8 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -12,6 +12,7 @@ function mod (x, n) { } export class WorldRendererThree extends WorldRendererCommon { + outputFormat = 'threeJs' as const blockEntities = {} sectionObjects: Record = {} showChunkBorders = false diff --git a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts index 15fa587ce..a8dcfff06 100644 --- a/prismarine-viewer/viewer/lib/worldrendererWebgl.ts +++ b/prismarine-viewer/viewer/lib/worldrendererWebgl.ts @@ -1,3 +1,4 @@ +import { Vec3 } from 'vec3' import { updateStatText } from '../../examples/newStats' import { addBlocksSection, removeBlocksSection, sendWorkerMessage } from '../../examples/webglRenderer' import type { WebglData } from '../prepare/webglData' @@ -5,13 +6,19 @@ import { loadJSON } from './utils.web' import { WorldRendererCommon } from './worldrendererCommon' export class WorldRendererWebgl extends WorldRendererCommon { + outputFormat = 'webgl' as const newChunks = {} as Record webglData: WebglData stopBlockUpdate = false - chunksLength = 0 + lastChunkDistance = 0 constructor(numWorkers = 4) { super(numWorkers) + + this.renderUpdateEmitter.on('update', () => { + const loadedChunks = Object.keys(this.finishedChunks).length; + updateStatText('loaded-chunks', `${loadedChunks}/${this.chunksLength} chunks (${this.lastChunkDistance})`) + }) } playgroundGetWebglData () { @@ -29,16 +36,25 @@ export class WorldRendererWebgl extends WorldRendererCommon { super.setBlockStateId(pos, stateId) } + isWaitingForChunksToRender = false + + allChunksLoaded (): void { + console.log('allChunksLoaded') + sendWorkerMessage({ + type: 'addBlocksSectionDone' + }) + } + handleWorkerMessage (data: any): void { if (data.type === 'geometry' && Object.keys(data.geometry.blocks).length) { - const chunkCoords = data.key.split(',') + const chunkCoords = data.key.split(',').map(Number) as [number, number, number] if (/* !this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || */ !this.active) return addBlocksSection(data.key, data.geometry) - const chunkDistance = Math.round(Math.max(Math.abs((chunkCoords[0]) - this.viewerPosition!.x) / 16, Math.abs(chunkCoords[2] - this.viewerPosition!.y) / 16)) - updateStatText('loaded-chunks', Object.keys(this.loadedChunks).length + `/${this.chunksLength} chunks (${chunkDistance})`) - // const blocks = Object.values(data.geometry.blocks) as any[] + this.lastChunkDistance = Math.max(...this.getDistance(new Vec3(chunkCoords[0], 0, chunkCoords[2]))) + + // todo this.newChunks[data.key] = data.geometry } } @@ -65,7 +81,6 @@ export class WorldRendererWebgl extends WorldRendererCommon { removeColumn (x, z) { - return console.log('removeColumn', x, z) super.removeColumn(x, z) for (const key of Object.keys(this.newChunks)) { From c5e505fe50c98d9c83bd2840a4958741a99fdec4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 25 May 2024 03:19:26 +0300 Subject: [PATCH 86/86] last comments --- prismarine-viewer/examples/webglRendererWorker.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/prismarine-viewer/examples/webglRendererWorker.ts b/prismarine-viewer/examples/webglRendererWorker.ts index 9f59ba59b..40f145dbb 100644 --- a/prismarine-viewer/examples/webglRendererWorker.ts +++ b/prismarine-viewer/examples/webglRendererWorker.ts @@ -501,6 +501,10 @@ onmessage = function (e) { // updateCubes?.(currentLength) } if (e.data.type === 'addBlocksSectionDone') { + // if (pendingUpdate) { + // console.log('Already pending') + // return + // } updateCubesWhenAvailable(lastNotUpdatedIndex) lastNotUpdatedIndex = undefined lastNotUpdatedArrSize = undefined @@ -566,7 +570,11 @@ onmessage = function (e) { } globalThis.testDuplicates = () => { - const duplicates = allSides.filter((value, index, self) => self.indexOf(value) !== index) + const duplicates = allSides.filter((arr, index, self) => { + return index !== self.findIndex((t) => { + return t[0] === arr[0] && t[1] === arr[1] && t[2] === arr[2] && t[3].face === arr[3].face + }) + }) console.log('duplicates', duplicates) }