feat: big fix: rendering blocks for preflat versions (<1.13)! (#125)
This commit is contained in:
parent
2bec255b7d
commit
5e94239091
8 changed files with 1880 additions and 12 deletions
|
|
@ -6,6 +6,7 @@ import path from 'path'
|
|||
import { fileURLToPath } from 'url'
|
||||
import fs from 'fs'
|
||||
import { dynamicMcDataFiles } from './buildMesherConfig.mjs'
|
||||
import { mesherSharedPlugins } from '../scripts/esbuildPlugins.mjs'
|
||||
|
||||
const allowedBundleFiles = ['legacy', 'versions', 'protocolVersions', 'features']
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ const buildOptions = {
|
|||
'process.env.BROWSER': '"true"',
|
||||
},
|
||||
plugins: [
|
||||
...mesherSharedPlugins,
|
||||
{
|
||||
name: 'external-json',
|
||||
setup (build) {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ self.onmessage = ({ data }) => {
|
|||
if (data.config) {
|
||||
world ??= new World(data.config.version)
|
||||
world.config = {...world.config, ...data.config}
|
||||
globalThis.world = world
|
||||
}
|
||||
|
||||
if (data.type === 'mesherData') {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Vec3 } from 'vec3'
|
||||
import type { BlockStatesOutput } from '../../prepare/modelsBuilder'
|
||||
import { World } from './world'
|
||||
import { Block } from 'prismarine-block'
|
||||
import { WorldBlock as Block } from './world'
|
||||
import legacyJson from '../../../../src/preflatMap.json'
|
||||
import { versionToNumber } from '../../prepare/utils'
|
||||
|
||||
const tints: any = {}
|
||||
let blockStates: BlockStatesOutput
|
||||
|
|
@ -33,6 +35,53 @@ function prepareTints (tints) {
|
|||
})
|
||||
}
|
||||
|
||||
const calculatedBlocksEntries = Object.entries(legacyJson.clientCalculatedBlocks);
|
||||
export function preflatBlockCalculation (block: Block, world: World, position: Vec3) {
|
||||
const type = calculatedBlocksEntries.find(([name, blocks]) => blocks.includes(block.name))?.[0]
|
||||
if (!type) return
|
||||
switch (type) {
|
||||
case 'directional': {
|
||||
const isSolidConnection = !block.name.includes('redstone') && !block.name.includes('tripwire')
|
||||
const neighbors = [
|
||||
world.getBlock(position.offset(0, 0, 1)),
|
||||
world.getBlock(position.offset(0, 0, -1)),
|
||||
world.getBlock(position.offset(1, 0, 0)),
|
||||
world.getBlock(position.offset(-1, 0, 0))
|
||||
]
|
||||
// set needed props to true: east:'false',north:'false',south:'false',west:'false'
|
||||
const props = {}
|
||||
for (const [i, neighbor] of neighbors.entries()) {
|
||||
const isConnectedToSolid = isSolidConnection ? (neighbor && !neighbor.transparent) : false
|
||||
if (isConnectedToSolid || neighbor?.name === block.name) {
|
||||
props[['south', 'north', 'east', 'west'][i]] = 'true'
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
// case 'gate_in_wall': {}
|
||||
case 'block_snowy': {
|
||||
const aboveIsSnow = world.getBlock(position.offset(0, 1, 0))?.name === 'snow'
|
||||
return {
|
||||
snowy: `${aboveIsSnow}`
|
||||
}
|
||||
}
|
||||
case 'door': {
|
||||
// upper half matches lower in
|
||||
const half = block.getProperties().half
|
||||
if (half === 'upper') {
|
||||
// copy other properties
|
||||
const lower = world.getBlock(position.offset(0, -1, 0))
|
||||
if (lower?.name === block.name) {
|
||||
return {
|
||||
...lower.getProperties(),
|
||||
half: 'upper'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tintToGl (tint) {
|
||||
const r = (tint >> 16) & 0xff
|
||||
const g = (tint >> 8) & 0xff
|
||||
|
|
@ -349,7 +398,7 @@ function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr
|
|||
|
||||
const aos: number[] = []
|
||||
const neighborPos = position.plus(new Vec3(...dir))
|
||||
const baseLight = world.getLight(neighborPos) / 15
|
||||
const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
|
||||
for (const pos of corners) {
|
||||
let vertex = [
|
||||
(pos[0] ? maxx : minx),
|
||||
|
|
@ -462,7 +511,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
|
||||
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
||||
const block = world.getBlock(cursor)!
|
||||
if (block.name.includes('_sign')) {
|
||||
if (block.name.includes('_sign') || block.name === 'sign') {
|
||||
const key = `${cursor.x},${cursor.y},${cursor.z}`
|
||||
const props: any = block.getProperties()
|
||||
const facingRotationMap = {
|
||||
|
|
@ -478,7 +527,24 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
}
|
||||
}
|
||||
const biome = block.biome.name
|
||||
if (block.variant === undefined) {
|
||||
|
||||
let preflatRecomputeVariant = !!(block as any)._originalProperties
|
||||
if (world.preflat) {
|
||||
const patchProperties = preflatBlockCalculation(block, world, cursor)
|
||||
if (patchProperties) {
|
||||
//@ts-ignore
|
||||
block._originalProperties ??= block._properties
|
||||
//@ts-ignore
|
||||
block._properties = { ...block._originalProperties, ...patchProperties }
|
||||
preflatRecomputeVariant = true
|
||||
} else {
|
||||
//@ts-ignore
|
||||
block._properties = block._originalProperties ?? block._properties
|
||||
//@ts-ignore
|
||||
block._originalProperties = undefined
|
||||
}
|
||||
}
|
||||
if (block.variant === undefined || preflatRecomputeVariant) {
|
||||
block.variant = getModelVariants(block)
|
||||
}
|
||||
|
||||
|
|
@ -592,7 +658,7 @@ function matchProperties (block: Block, /* to match against */properties: Record
|
|||
return true
|
||||
}
|
||||
|
||||
function getModelVariants (block: import('prismarine-block').Block) {
|
||||
function getModelVariants (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 []
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Block } from "prismarine-block"
|
|||
import { Vec3 } from 'vec3'
|
||||
import moreBlockDataGeneratedJson from '../moreBlockDataGenerated.json'
|
||||
import { defaultMesherConfig } from './shared'
|
||||
import legacyJson from '../../../../src/preflatMap.json'
|
||||
|
||||
const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions)
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ function isCube (shapes) {
|
|||
return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1
|
||||
}
|
||||
|
||||
export type WorldBlock = Block & {
|
||||
export type WorldBlock = Omit<Block, 'position'> & {
|
||||
variant?: any
|
||||
// todo
|
||||
isCube: boolean
|
||||
|
|
@ -30,14 +31,16 @@ export class World {
|
|||
columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk }
|
||||
blockCache = {}
|
||||
biomeCache: { [id: number]: mcData.Biome }
|
||||
preflat: boolean
|
||||
|
||||
constructor(version) {
|
||||
this.Chunk = Chunks(version) as any
|
||||
this.biomeCache = mcData(version).biomes
|
||||
this.preflat = !mcData(version).supportFeature('blockStateId')
|
||||
this.config.version = version
|
||||
}
|
||||
|
||||
getLight (pos: Vec3, isNeighbor = false) {
|
||||
getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
|
||||
const { enableLighting, skyLight } = this.config
|
||||
if (!enableLighting) return 15
|
||||
// const key = `${pos.x},${pos.y},${pos.z}`
|
||||
|
|
@ -52,8 +55,17 @@ export class World {
|
|||
) + 2
|
||||
)
|
||||
// lightsCache.set(key, result)
|
||||
if (result === 2 && this.getBlock(pos)?.name.match(/_stairs|slab/)) { // todo this is obviously wrong
|
||||
result = this.getLight(pos.offset(0, 1, 0))
|
||||
if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => x.match(/_stairs|slab|glass_pane/)) && !skipMoreChecks) { // todo this is obviously wrong
|
||||
const lights = [
|
||||
this.getLight(pos.offset(0, 1, 0), undefined, true),
|
||||
this.getLight(pos.offset(0, -1, 0), undefined, true),
|
||||
this.getLight(pos.offset(0, 0, 1), undefined, true),
|
||||
this.getLight(pos.offset(0, 0, -1), undefined, true),
|
||||
this.getLight(pos.offset(1, 0, 0), undefined, true),
|
||||
this.getLight(pos.offset(-1, 0, 0), undefined, true)
|
||||
].filter(x => x !== 2)
|
||||
const min = Math.min(...lights)
|
||||
result = min
|
||||
}
|
||||
if (isNeighbor && result === 2) result = 15 // TODO
|
||||
return result
|
||||
|
|
@ -91,6 +103,8 @@ export class World {
|
|||
}
|
||||
|
||||
getBlock (pos: Vec3): WorldBlock | null {
|
||||
// for easier testing
|
||||
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
|
||||
const key = columnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
|
||||
|
||||
const column = this.columns[key]
|
||||
|
|
@ -111,6 +125,23 @@ export class World {
|
|||
throw new Error('position is not reliable, use pos parameter instead of block.position')
|
||||
}
|
||||
})
|
||||
if (this.preflat) {
|
||||
const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || legacyJson.blocks[b.type + ':' + '0']
|
||||
b.name = namePropsStr.split('[')[0]
|
||||
const propsStr = namePropsStr.split('[')?.[1]?.split(']');
|
||||
if (propsStr) {
|
||||
const newProperties = Object.fromEntries(propsStr.join('').split(',').map(x => {
|
||||
let [key, val] = x.split('=') as any
|
||||
if (!isNaN(val)) val = parseInt(val)
|
||||
return [key, val]
|
||||
}))
|
||||
//@ts-ignore
|
||||
b._properties = newProperties
|
||||
} else {
|
||||
//@ts-ignore
|
||||
b._properties = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const block = this.blockCache[stateId]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import EventEmitter from 'events'
|
|||
import { WorldRendererThree } from './worldrendererThree'
|
||||
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
||||
import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon'
|
||||
import { versionToNumber } from '../prepare/utils'
|
||||
|
||||
export class Viewer {
|
||||
scene: THREE.Scene
|
||||
|
|
@ -76,7 +77,8 @@ export class Viewer {
|
|||
}
|
||||
|
||||
setVersion (userVersion: string) {
|
||||
const texturesVersion = getVersion(userVersion)
|
||||
let texturesVersion = getVersion(userVersion)
|
||||
if (versionToNumber(userVersion) < versionToNumber('1.13')) texturesVersion = '1.13.2' // we normalize to post-flatenning in mesher
|
||||
console.log('[viewer] Using version:', userVersion, 'textures:', texturesVersion)
|
||||
this.world.setVersion(userVersion, texturesVersion)
|
||||
this.entities.clear()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import mcAssets from 'minecraft-assets'
|
|||
import fs from 'fs-extra'
|
||||
import { prepareMoreGeneratedBlocks } from './moreGeneratedBlocks'
|
||||
import { generateItemsAtlases } from './genItemsAtlas'
|
||||
import { versionToNumber } from './utils'
|
||||
|
||||
const publicPath = path.resolve(__dirname, '../../public')
|
||||
|
||||
|
|
@ -23,6 +24,10 @@ Promise.resolve().then(async () => {
|
|||
if (!mcAssets.versions.includes(version)) {
|
||||
throw new Error(`Version ${version} is not supported by minecraft-assets`)
|
||||
}
|
||||
if (versionToNumber(version) < versionToNumber('1.13')) {
|
||||
// we normalize data to 1.13 for pre 1.13 versions
|
||||
continue
|
||||
}
|
||||
const assets = mcAssets(version)
|
||||
const { warnings: _warnings } = await prepareMoreGeneratedBlocks(assets)
|
||||
_warnings.forEach(x => warnings.add(x))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import MCProtocol from 'minecraft-protocol'
|
|||
import MCData from 'minecraft-data'
|
||||
import { throttle } from 'lodash-es'
|
||||
|
||||
const __dirname = dirname(new URL(import.meta.url).pathname)
|
||||
const { supportedVersions } = MCProtocol
|
||||
|
||||
const prod = process.argv.includes('--prod')
|
||||
|
|
@ -26,7 +27,7 @@ export const startWatchingHmr = () => {
|
|||
// 'dist/webglRendererWorker.js': 'webglRendererWorker',
|
||||
}
|
||||
for (const name of Object.keys(eventsPerFile)) {
|
||||
const file = join('dist', name);
|
||||
const file = join('dist', name)
|
||||
if (!fs.existsSync(file)) console.warn(`[missing worker] File ${name} does not exist`)
|
||||
fs.watchFile(file, () => {
|
||||
writeToClients({ replace: { type: eventsPerFile[name] } })
|
||||
|
|
@ -34,8 +35,27 @@ export const startWatchingHmr = () => {
|
|||
}
|
||||
}
|
||||
|
||||
/** @type {import('esbuild').Plugin[]} */
|
||||
const mesherSharedPlugins = [
|
||||
{
|
||||
name: 'minecraft-data',
|
||||
setup (build) {
|
||||
build.onLoad({
|
||||
filter: /data[\/\\]pc[\/\\]common[\/\\]legacy.json$/,
|
||||
}, async (args) => {
|
||||
const data = fs.readFileSync(join(__dirname, '../src/preflatMap.json'), 'utf8')
|
||||
return {
|
||||
contents: `module.exports = ${data}`,
|
||||
loader: 'js',
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
/** @type {import('esbuild').Plugin[]} */
|
||||
const plugins = [
|
||||
...mesherSharedPlugins,
|
||||
{
|
||||
name: 'strict-aliases',
|
||||
setup (build) {
|
||||
|
|
@ -339,4 +359,4 @@ const plugins = [
|
|||
})
|
||||
]
|
||||
|
||||
export { plugins, connectedClients as clients }
|
||||
export { plugins, connectedClients as clients, mesherSharedPlugins }
|
||||
|
|
|
|||
1741
src/preflatMap.json
Normal file
1741
src/preflatMap.json
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue