diff --git a/viewer/lib/atlas.js b/viewer/lib/atlas.js index 7f45f03c..d36ccca3 100644 --- a/viewer/lib/atlas.js +++ b/viewer/lib/atlas.js @@ -13,9 +13,18 @@ function nextPowerOfTwo (n) { return n + 1 } +function readTexture (basePath, name) { + if (name === 'missing_texture.png') { + // grab ./missing_texture.png + basePath = __dirname + } + return fs.readFileSync(path.join(basePath, name), 'base64') +} + function makeTextureAtlas (mcAssets) { const blocksTexturePath = path.join(mcAssets.directory, '/blocks') const textureFiles = fs.readdirSync(blocksTexturePath).filter(file => file.endsWith('.png')) + textureFiles.unshift('missing_texture.png') const texSize = nextPowerOfTwo(Math.ceil(Math.sqrt(textureFiles.length))) const tileSize = 16 @@ -35,7 +44,7 @@ function makeTextureAtlas (mcAssets) { texturesIndex[name] = { u: x / imgSize, v: y / imgSize, su: tileSize / imgSize, sv: tileSize / imgSize } const img = new Image() - img.src = 'data:image/png;base64,' + fs.readFileSync(path.join(blocksTexturePath, textureFiles[i]), 'base64') + img.src = 'data:image/png;base64,' + readTexture(blocksTexturePath, textureFiles[i]) g.drawImage(img, 0, 0, 16, 16, x, y, 16, 16) } diff --git a/viewer/lib/missing_texture.png b/viewer/lib/missing_texture.png new file mode 100644 index 00000000..affd9d68 Binary files /dev/null and b/viewer/lib/missing_texture.png differ diff --git a/viewer/lib/models.js b/viewer/lib/models.js index 5ae9535b..9f8938ec 100644 --- a/viewer/lib/models.js +++ b/viewer/lib/models.js @@ -264,8 +264,8 @@ function renderElement (world, cursor, element, doAO, attr, globalMatrix, global 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') { + 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] @@ -478,7 +478,9 @@ function matchProperties (block, properties) { } function getModelVariants (block, blockStates) { - const state = blockStates[block.name] + // air, cave_air, void_air and so on... + if (block.name.includes('air')) return [] + const state = blockStates[block.name] ?? blockStates.missing_texture if (!state) return [] if (state.variants) { for (const [properties, variant] of Object.entries(state.variants)) { diff --git a/viewer/lib/modelsBuilder.js b/viewer/lib/modelsBuilder.js index ecd1bc35..1a6ab5cb 100644 --- a/viewer/lib/modelsBuilder.js +++ b/viewer/lib/modelsBuilder.js @@ -34,6 +34,7 @@ function getModel (name, blocksModels) { } function prepareModel (model, texturesJson) { + // resolve texture names eg west: #all -> blocks/stone for (const tex in model.textures) { let root = model.textures[tex] while (root.charAt(0) === '#') { @@ -94,6 +95,19 @@ function resolveModel (name, blocksModels, texturesJson) { function prepareBlocksStates (mcAssets, atlas) { const blocksStates = mcAssets.blocksStates + mcAssets.blocksStates.missing_texture = { + variants: { + normal: { + model: 'missing_texture' + } + } + } + mcAssets.blocksModels.missing_texture = { + parent: 'block/cube_all', + textures: { + all: 'blocks/missing_texture' + } + } for (const block of Object.values(blocksStates)) { if (!block) continue if (block.variants) { diff --git a/viewer/lib/utils.js b/viewer/lib/utils.js index e91ff69f..382c0573 100644 --- a/viewer/lib/utils.js +++ b/viewer/lib/utils.js @@ -10,7 +10,12 @@ const THREE = require('three') const path = require('path') const textureCache = {} +// todo not ideal, export different functions for browser and node function loadTexture (texture, cb) { + if (process.platform === 'browser') { + return require('./utils.web').loadTexture(texture, cb) + } + if (textureCache[texture]) { cb(textureCache[texture]) } else { @@ -22,6 +27,9 @@ function loadTexture (texture, cb) { } function loadJSON (json, cb) { + if (process.platform === 'browser') { + return require('./utils.web').loadJSON(json, cb) + } cb(require(path.resolve(__dirname, '../../public/' + json))) } diff --git a/viewer/lib/viewer.js b/viewer/lib/viewer.js index 14d64a45..db02d904 100644 --- a/viewer/lib/viewer.js +++ b/viewer/lib/viewer.js @@ -27,6 +27,14 @@ class Viewer { this.primitives = new Primitives(this.scene, this.camera) this.domElement = renderer.domElement + this.playerHeight = 1.6 + this.isSneaking = false + } + + resetAll () { + this.world.resetWorld() + this.entities.clear() + this.primitives.clear() } setVersion (version) { @@ -66,7 +74,11 @@ class Viewer { } setFirstPersonCamera (pos, yaw, pitch) { - if (pos) new TWEEN.Tween(this.camera.position).to({ x: pos.x, y: pos.y + 1.6, z: pos.z }, 50).start() + if (pos) { + let y = pos.y + this.playerHeight + if (this.isSneaking) y -= 0.3 + new TWEEN.Tween(this.camera.position).to({ x: pos.x, y, z: pos.z }, 50).start() + } this.camera.rotation.set(pitch, yaw, 0, 'ZYX') } diff --git a/viewer/lib/worker.js b/viewer/lib/worker.js index ad76d37e..b7431677 100644 --- a/viewer/lib/worker.js +++ b/viewer/lib/worker.js @@ -54,6 +54,9 @@ self.onmessage = ({ data }) => { } else if (data.type === 'blockUpdate') { const loc = new Vec3(data.pos.x, data.pos.y, data.pos.z).floored() world.setBlockStateId(loc, data.stateId) + } else if (data.type === 'reset') { + world = null + blocksStates = null } } diff --git a/viewer/lib/worldView.js b/viewer/lib/worldView.js index ad0ffa44..ec2ccd98 100644 --- a/viewer/lib/worldView.js +++ b/viewer/lib/worldView.js @@ -102,10 +102,10 @@ class WorldView extends EventEmitter { delete this.loadedChunks[`${pos.x},${pos.z}`] } - async updatePosition (pos) { + async updatePosition (pos, force = false) { const [lastX, lastZ] = chunkPos(this.lastPos) const [botX, botZ] = chunkPos(pos) - if (lastX !== botX || lastZ !== botZ) { + if (lastX !== botX || lastZ !== botZ || force) { const newView = new ViewRect(botX, botZ, this.viewDistance) for (const coords of Object.keys(this.loadedChunks)) { const x = parseInt(coords.split(',')[0]) diff --git a/viewer/lib/worldrenderer.js b/viewer/lib/worldrenderer.js index b464401d..744224a7 100644 --- a/viewer/lib/worldrenderer.js +++ b/viewer/lib/worldrenderer.js @@ -12,10 +12,14 @@ function mod (x, n) { class WorldRenderer { constructor (scene, numWorkers = 4) { this.sectionMeshs = {} + this.active = false + this.version = undefined this.scene = scene this.loadedChunks = {} this.sectionsOutstanding = new Set() this.renderUpdateEmitter = new EventEmitter() + this.blockStatesData = undefined + this.texturesDataUrl = undefined this.material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) @@ -60,23 +64,43 @@ class WorldRenderer { } } - setVersion (version) { + resetWorld () { + this.active = false for (const mesh of Object.values(this.sectionMeshs)) { this.scene.remove(mesh) } this.sectionMeshs = {} + for (const worker of this.workers) { + worker.postMessage({ type: 'reset' }) + } + } + + setVersion (version) { + this.version = version + this.resetWorld() + this.active = true for (const worker of this.workers) { worker.postMessage({ type: 'version', version }) } - loadTexture(`textures/${version}.png`, texture => { + this.updateTexturesData() + } + + updateTexturesData () { + loadTexture(this.texturesDataUrl || `textures/${this.version}.png`, texture => { texture.magFilter = THREE.NearestFilter texture.minFilter = THREE.NearestFilter texture.flipY = false this.material.map = texture }) - loadJSON(`blocksStates/${version}.json`, blockStates => { + const loadBlockStates = () => { + return new Promise(resolve => { + if (this.blockStatesData) return resolve(this.blockStatesData) + return loadJSON(`blocksStates/${this.version}.json`, resolve) + }) + } + loadBlockStates().then((blockStates) => { for (const worker of this.workers) { worker.postMessage({ type: 'blockStates', json: blockStates }) } diff --git a/viewer/prerender.js b/viewer/prerender.js index be0c25f7..8f525adb 100644 --- a/viewer/prerender.js +++ b/viewer/prerender.js @@ -5,14 +5,14 @@ const mcAssets = require('minecraft-assets') const fs = require('fs-extra') const texturesPath = path.resolve(__dirname, '../public/textures') -if (!fs.existsSync(texturesPath)) { - fs.mkdirSync(texturesPath) +if (fs.existsSync(texturesPath) && !process.argv.includes('-f')) { + console.log('textures folder already exists, skipping...') + process.exit(0) } +fs.mkdirSync(texturesPath, { recursive: true }) const blockStatesPath = path.resolve(__dirname, '../public/blocksStates') -if (!fs.existsSync(blockStatesPath)) { - fs.mkdirSync(blockStatesPath) -} +fs.mkdirSync(blockStatesPath, { recursive: true }) const supportedVersions = require('./lib/version').supportedVersions