Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

World rendering improvements #408

Merged
merged 11 commits into from
Dec 31, 2023
11 changes: 10 additions & 1 deletion viewer/lib/atlas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

Expand Down
Binary file added viewer/lib/missing_texture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions viewer/lib/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)) {
Expand Down
14 changes: 14 additions & 0 deletions viewer/lib/modelsBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) === '#') {
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions viewer/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed? didn't it work before?

}

if (textureCache[texture]) {
cb(textureCache[texture])
} else {
Expand All @@ -22,6 +27,9 @@ function loadTexture (texture, cb) {
}

function loadJSON (json, cb) {
if (process.platform === 'browser') {
return require('./utils.web').loadJSON(json, cb)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed? didn't it work before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said in the body I moved to esbuild in my p viewer setup so I guess that's why I added it. If this is the only thing that blocks merging the pr and you don't want to see it you can remove it (feel free to do it since you should have push access)

}
cb(require(path.resolve(__dirname, '../../public/' + json)))
}

Expand Down
14 changes: 13 additions & 1 deletion viewer/lib/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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')
}

Expand Down
3 changes: 3 additions & 0 deletions viewer/lib/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
4 changes: 2 additions & 2 deletions viewer/lib/worldView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
30 changes: 27 additions & 3 deletions viewer/lib/worldrenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })

Expand Down Expand Up @@ -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 = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed? didn't it work before?

Copy link
Contributor Author

@zardoy zardoy Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still works, but now you can override logic of getting block states via exposed property so you can skip network request. Ideally it should also set to this property if wasn't set before after request, fixed in my version but not here but that's not a big issue imo. Overall it's not a bug fix, it's a customisation improvement

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 })
}
Expand Down
10 changes: 5 additions & 5 deletions viewer/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading