720 lines
26 KiB
TypeScript
720 lines
26 KiB
TypeScript
import { Vec3 } from 'vec3'
|
|
import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
import legacyJson from '../../../../src/preflatMap.json'
|
|
import { BlockType } from '../../../playground/shared'
|
|
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock } from './world'
|
|
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
import { INVISIBLE_BLOCKS } from './worldConstants'
|
|
import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
|
|
|
|
let blockProvider: WorldBlockProvider
|
|
|
|
const tints: any = {}
|
|
let needTiles = false
|
|
|
|
let tintsData
|
|
try {
|
|
tintsData = require('esbuild-data').tints
|
|
} catch (err) {
|
|
tintsData = require('minecraft-data/minecraft-data/data/pc/1.16.2/tints.json')
|
|
}
|
|
for (const key of Object.keys(tintsData)) {
|
|
tints[key] = prepareTints(tintsData[key])
|
|
}
|
|
|
|
type Tiles = {
|
|
[blockPos: string]: BlockType
|
|
}
|
|
|
|
function prepareTints (tints) {
|
|
const map = new Map()
|
|
const defaultValue = tintToGl(tints.default)
|
|
for (let { keys, color } of tints.data) {
|
|
color = tintToGl(color)
|
|
for (const key of keys) {
|
|
map.set(`${key}`, color)
|
|
}
|
|
}
|
|
return new Proxy(map, {
|
|
get (target, key) {
|
|
return target.has(key) ? target.get(key) : defaultValue
|
|
}
|
|
})
|
|
}
|
|
|
|
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 = {}
|
|
let changed = false
|
|
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'
|
|
changed = true
|
|
}
|
|
}
|
|
return changed ? props : undefined
|
|
}
|
|
// case 'gate_in_wall': {}
|
|
case 'block_snowy': {
|
|
const aboveIsSnow = world.getBlock(position.offset(0, 1, 0))?.name === 'snow'
|
|
if (aboveIsSnow) {
|
|
return {
|
|
snowy: `${aboveIsSnow}`
|
|
}
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
case 'door': {
|
|
// upper half matches lower in
|
|
const { half } = block.getProperties()
|
|
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
|
|
const b = tint & 0xff
|
|
return [r / 255, g / 255, b / 255]
|
|
}
|
|
|
|
function getLiquidRenderHeight (world: World, block: WorldBlock | null, type: number, pos: Vec3, isWater: boolean, isRealWater: boolean) {
|
|
if ((isWater && !isRealWater) || (block && isBlockWaterlogged(block))) return 8 / 9
|
|
if (!block || block.type !== type) return 1 / 9
|
|
if (block.metadata === 0) { // source block
|
|
const blockAbove = world.getBlock(pos.offset(0, 1, 0))
|
|
if (blockAbove && blockAbove.type === type) return 1
|
|
return 8 / 9
|
|
}
|
|
return ((block.metadata >= 8 ? 8 : 7 - block.metadata) + 1) / 9
|
|
}
|
|
|
|
|
|
const isCube = (block: Block) => {
|
|
if (!block || block.transparent) return false
|
|
if (block.isCube) return true
|
|
if (!block.models?.length || block.models.length !== 1) return false
|
|
// all variants
|
|
return block.models[0].every(v => v.elements.every(e => {
|
|
return e.from[0] === 0 && e.from[1] === 0 && e.from[2] === 0 && e.to[0] === 16 && e.to[1] === 16 && e.to[2] === 16
|
|
}))
|
|
}
|
|
|
|
const getVec = (v: Vec3, dir: Vec3) => {
|
|
for (const coord of ['x', 'y', 'z']) {
|
|
if (Math.abs(dir[coord]) > 0) v[coord] = 0
|
|
}
|
|
return v.plus(dir)
|
|
}
|
|
|
|
function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record<string, any>, isRealWater: boolean) {
|
|
const heights: number[] = []
|
|
for (let z = -1; z <= 1; z++) {
|
|
for (let x = -1; x <= 1; x++) {
|
|
const pos = cursor.offset(x, 0, z)
|
|
heights.push(getLiquidRenderHeight(world, world.getBlock(pos), type, pos, water, isRealWater))
|
|
}
|
|
}
|
|
const cornerHeights = [
|
|
Math.max(Math.max(heights[0], heights[1]), Math.max(heights[3], heights[4])),
|
|
Math.max(Math.max(heights[1], heights[2]), Math.max(heights[4], heights[5])),
|
|
Math.max(Math.max(heights[3], heights[4]), Math.max(heights[6], heights[7])),
|
|
Math.max(Math.max(heights[4], heights[5]), Math.max(heights[7], heights[8]))
|
|
]
|
|
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const face in elemFaces) {
|
|
const { dir, corners, mask1, mask2 } = elemFaces[face]
|
|
const isUp = dir[1] === 1
|
|
|
|
const neighborPos = cursor.offset(...dir as [number, number, number])
|
|
const neighbor = world.getBlock(neighborPos)
|
|
if (!neighbor) continue
|
|
if (neighbor.type === type || (water && (neighbor.name === 'water' || isBlockWaterlogged(neighbor)))) continue
|
|
if (isCube(neighbor) && !isUp) continue
|
|
|
|
let tint = [1, 1, 1]
|
|
if (water) {
|
|
let m = 1 // Fake lighting to improve lisibility
|
|
if (Math.abs(dir[0]) > 0) m = 0.6
|
|
else if (Math.abs(dir[2]) > 0) m = 0.8
|
|
tint = tints.water[biome]
|
|
tint = [tint[0] * m, tint[1] * m, tint[2] * m]
|
|
}
|
|
|
|
if (needTiles) {
|
|
const tiles = attr.tiles as Tiles
|
|
tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= {
|
|
block: 'water',
|
|
faces: [],
|
|
}
|
|
tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({
|
|
face,
|
|
neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`,
|
|
side: 0, // todo
|
|
textureIndex: 0,
|
|
// texture: eFace.texture.name,
|
|
})
|
|
}
|
|
|
|
const { u } = texture
|
|
const { v } = texture
|
|
const { su } = texture
|
|
const { sv } = texture
|
|
|
|
// Get base light value for the face
|
|
const baseLight = world.getLight(neighborPos, undefined, undefined, water ? 'water' : 'lava') / 15
|
|
|
|
for (const pos of corners) {
|
|
const height = cornerHeights[pos[2] * 2 + pos[0]]
|
|
attr.t_positions.push(
|
|
(pos[0] ? 0.999 : 0.001) + (cursor.x & 15) - 8,
|
|
(pos[1] ? height - 0.001 : 0.001) + (cursor.y & 15) - 8,
|
|
(pos[2] ? 0.999 : 0.001) + (cursor.z & 15) - 8
|
|
)
|
|
attr.t_normals.push(...dir)
|
|
attr.t_uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v)
|
|
|
|
let cornerLightResult = baseLight
|
|
if (world.config.smoothLighting) {
|
|
const dx = pos[0] * 2 - 1
|
|
const dy = pos[1] * 2 - 1
|
|
const dz = pos[2] * 2 - 1
|
|
const cornerDir: [number, number, number] = [dx, dy, dz]
|
|
const side1Dir: [number, number, number] = [dx * mask1[0], dy * mask1[1], dz * mask1[2]]
|
|
const side2Dir: [number, number, number] = [dx * mask2[0], dy * mask2[1], dz * mask2[2]]
|
|
|
|
const dirVec = new Vec3(...dir as [number, number, number])
|
|
|
|
const side1LightDir = getVec(new Vec3(...side1Dir), dirVec)
|
|
const side1Light = world.getLight(cursor.plus(side1LightDir)) / 15
|
|
const side2DirLight = getVec(new Vec3(...side2Dir), dirVec)
|
|
const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15
|
|
const cornerLightDir = getVec(new Vec3(...cornerDir), dirVec)
|
|
const cornerLight = world.getLight(cursor.plus(cornerLightDir)) / 15
|
|
// interpolate
|
|
const lights = [side1Light, side2Light, cornerLight, baseLight]
|
|
cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length
|
|
}
|
|
|
|
// Apply light value to tint
|
|
attr.t_colors.push(tint[0] * cornerLightResult, tint[1] * cornerLightResult, tint[2] * cornerLightResult)
|
|
}
|
|
}
|
|
}
|
|
|
|
const identicalCull = (currentElement: BlockElement, neighbor: Block, direction: Vec3) => {
|
|
const dirStr = `${direction.x},${direction.y},${direction.z}`
|
|
const lookForOppositeSide = {
|
|
'0,1,0': 'down',
|
|
'0,-1,0': 'up',
|
|
'1,0,0': 'east',
|
|
'-1,0,0': 'west',
|
|
'0,0,1': 'south',
|
|
'0,0,-1': 'north',
|
|
}[dirStr]!
|
|
const elemCompareForm = {
|
|
'0,1,0': (e: BlockElement) => `${e.from[0]},${e.from[2]}:${e.to[0]},${e.to[2]}`,
|
|
'0,-1,0': (e: BlockElement) => `${e.to[0]},${e.to[2]}:${e.from[0]},${e.from[2]}`,
|
|
'1,0,0': (e: BlockElement) => `${e.from[2]},${e.from[1]}:${e.to[2]},${e.to[1]}`,
|
|
'-1,0,0': (e: BlockElement) => `${e.to[2]},${e.to[1]}:${e.from[2]},${e.from[1]}`,
|
|
'0,0,1': (e: BlockElement) => `${e.from[1]},${e.from[2]}:${e.to[1]},${e.to[2]}`,
|
|
'0,0,-1': (e: BlockElement) => `${e.to[1]},${e.to[2]}:${e.from[1]},${e.from[2]}`,
|
|
}[dirStr]!
|
|
const elementEdgeValidator = {
|
|
'0,1,0': (e: BlockElement) => currentElement.from[1] === 0 && e.to[2] === 16,
|
|
'0,-1,0': (e: BlockElement) => currentElement.from[1] === 0 && e.to[2] === 16,
|
|
'1,0,0': (e: BlockElement) => currentElement.from[0] === 0 && e.to[1] === 16,
|
|
'-1,0,0': (e: BlockElement) => currentElement.from[0] === 0 && e.to[1] === 16,
|
|
'0,0,1': (e: BlockElement) => currentElement.from[2] === 0 && e.to[0] === 16,
|
|
'0,0,-1': (e: BlockElement) => currentElement.from[2] === 0 && e.to[0] === 16,
|
|
}[dirStr]!
|
|
const useVar = 0
|
|
const models = neighbor.models?.map(m => m[useVar] ?? m[0]) ?? []
|
|
// TODO we should support it! rewrite with optimizing general pipeline
|
|
if (models.some(m => m.x || m.y || m.z)) return
|
|
return models.every(model => {
|
|
return (model.elements ?? []).every(element => {
|
|
// todo check alfa on texture
|
|
return !!(element.faces[lookForOppositeSide]?.cullface && elemCompareForm(currentElement) === elemCompareForm(element) && elementEdgeValidator(element))
|
|
})
|
|
})
|
|
}
|
|
|
|
let needSectionRecomputeOnChange = false
|
|
|
|
function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) {
|
|
const position = cursor
|
|
// const key = `${position.x},${position.y},${position.z}`
|
|
// if (!globalThis.allowedBlocks.includes(key)) return
|
|
const cullIfIdentical = block.name.includes('glass') || block.name.includes('ice')
|
|
|
|
// eslint-disable-next-line guard-for-in
|
|
for (const face in element.faces) {
|
|
const eFace = element.faces[face]
|
|
const { corners, mask1, mask2, side } = elemFaces[face]
|
|
const dir = matmul3(globalMatrix, elemFaces[face].dir)
|
|
|
|
if (eFace.cullface) {
|
|
const neighbor = world.getBlock(cursor.plus(new Vec3(...dir)), blockProvider, {})
|
|
if (neighbor) {
|
|
if (cullIfIdentical && neighbor.stateId === block.stateId) continue
|
|
if (!neighbor.transparent && (isCube(neighbor) || identicalCull(element, neighbor, new Vec3(...dir)))) continue
|
|
} else {
|
|
needSectionRecomputeOnChange = true
|
|
// 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 texture = eFace.texture as any
|
|
const { u } = texture
|
|
const { v } = texture
|
|
const { su } = texture
|
|
const { sv } = texture
|
|
|
|
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
|
|
let r = eFace.rotation || 0
|
|
if (face === 'down') {
|
|
r += 180
|
|
}
|
|
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 && !needTiles) {
|
|
// todo do we support rescale?
|
|
localMatrix = buildRotationMatrix(
|
|
element.rotation.axis,
|
|
element.rotation.angle
|
|
)
|
|
|
|
localShift = vecsub3(
|
|
element.rotation.origin,
|
|
matmul3(
|
|
localMatrix,
|
|
element.rotation.origin
|
|
)
|
|
)
|
|
}
|
|
|
|
const aos: number[] = []
|
|
const neighborPos = position.plus(new Vec3(...dir))
|
|
// 10%
|
|
const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
|
|
for (const pos of corners) {
|
|
let vertex = [
|
|
(pos[0] ? maxx : minx),
|
|
(pos[1] ? maxy : miny),
|
|
(pos[2] ? maxz : minz)
|
|
]
|
|
|
|
if (!needTiles) { // 10%
|
|
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
|
|
const { smoothLighting } = world.config
|
|
// const smoothLighting = true
|
|
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))
|
|
|
|
let cornerLightResult = baseLight * 15
|
|
|
|
if (smoothLighting) {
|
|
const dirVec = new Vec3(...dir)
|
|
const getVec = (v: Vec3) => {
|
|
for (const coord of ['x', 'y', 'z']) {
|
|
if (Math.abs(dirVec[coord]) > 0) v[coord] = 0
|
|
}
|
|
return v.plus(dirVec)
|
|
}
|
|
const side1LightDir = getVec(new Vec3(...side1Dir))
|
|
const side1Light = world.getLight(cursor.plus(side1LightDir))
|
|
const side2DirLight = getVec(new Vec3(...side2Dir))
|
|
const side2Light = world.getLight(cursor.plus(side2DirLight))
|
|
const cornerLightDir = getVec(new Vec3(...cornerDir))
|
|
const cornerLight = world.getLight(cursor.plus(cornerLightDir))
|
|
// interpolate
|
|
const lights = [side1Light, side2Light, cornerLight, baseLight * 15]
|
|
cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length
|
|
}
|
|
|
|
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))
|
|
// todo light should go upper on lower blocks
|
|
light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
aos.push(ao)
|
|
}
|
|
|
|
if (!needTiles) {
|
|
attr.colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
|
|
}
|
|
}
|
|
|
|
const lightWithColor = [baseLight * tint[0], baseLight * tint[1], baseLight * tint[2]] as [number, number, number]
|
|
|
|
if (needTiles) {
|
|
const tiles = attr.tiles as Tiles
|
|
tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= {
|
|
block: block.name,
|
|
faces: [],
|
|
}
|
|
const needsOnlyOneFace = false
|
|
const isTilesEmpty = tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.length < 1
|
|
if (isTilesEmpty || !needsOnlyOneFace) {
|
|
tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({
|
|
face,
|
|
side,
|
|
textureIndex: eFace.texture.tileIndex,
|
|
neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`,
|
|
light: baseLight,
|
|
tint: lightWithColor,
|
|
//@ts-expect-error debug prop
|
|
texture: eFace.texture.debugName || block.name,
|
|
} satisfies BlockType['faces'][number])
|
|
}
|
|
}
|
|
|
|
if (!needTiles) {
|
|
if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) {
|
|
attr.indices[attr.indicesCount++] = ndx
|
|
attr.indices[attr.indicesCount++] = ndx + 3
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
attr.indices[attr.indicesCount++] = ndx + 3
|
|
} else {
|
|
attr.indices[attr.indicesCount++] = ndx
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
attr.indices[attr.indicesCount++] = ndx + 3
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const ALWAYS_WATERLOGGED = new Set([
|
|
'seagrass',
|
|
'tall_seagrass',
|
|
'kelp',
|
|
'kelp_plant',
|
|
'bubble_column'
|
|
])
|
|
const isBlockWaterlogged = (block: Block) => {
|
|
return block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true' || ALWAYS_WATERLOGGED.has(block.name)
|
|
}
|
|
|
|
let unknownBlockModel: BlockModelPartsResolved
|
|
export function getSectionGeometry (sx, sy, sz, world: World) {
|
|
let delayedRender = [] as Array<() => void>
|
|
|
|
const attr: MesherGeometryOutput = {
|
|
sx: sx + 8,
|
|
sy: sy + 8,
|
|
sz: sz + 8,
|
|
positions: [],
|
|
normals: [],
|
|
colors: [],
|
|
uvs: [],
|
|
t_positions: [],
|
|
t_normals: [],
|
|
t_colors: [],
|
|
t_uvs: [],
|
|
indices: [],
|
|
indicesCount: 0, // Track current index position
|
|
using32Array: true,
|
|
tiles: {},
|
|
// todo this can be removed here
|
|
heads: {},
|
|
signs: {},
|
|
// isFull: true,
|
|
highestBlocks: new Map(),
|
|
hadErrors: false,
|
|
blocksCount: 0
|
|
}
|
|
|
|
const cursor = new Vec3(0, 0, 0)
|
|
for (cursor.y = sy; cursor.y < sy + 16; cursor.y++) {
|
|
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
|
|
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
|
let block = world.getBlock(cursor, blockProvider, attr)!
|
|
if (!INVISIBLE_BLOCKS.has(block.name)) {
|
|
const highest = attr.highestBlocks.get(`${cursor.x},${cursor.z}`)
|
|
if (!highest || highest.y < cursor.y) {
|
|
attr.highestBlocks.set(`${cursor.x},${cursor.z}`, { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id })
|
|
}
|
|
}
|
|
if (INVISIBLE_BLOCKS.has(block.name)) continue
|
|
if ((block.name.includes('_sign') || block.name === 'sign') && !world.config.disableSignsMapsSupport) {
|
|
const key = `${cursor.x},${cursor.y},${cursor.z}`
|
|
const props: any = block.getProperties()
|
|
const facingRotationMap = {
|
|
'north': 2,
|
|
'south': 0,
|
|
'west': 1,
|
|
'east': 3
|
|
}
|
|
const isWall = block.name.endsWith('wall_sign') || block.name.endsWith('wall_hanging_sign')
|
|
const isHanging = block.name.endsWith('hanging_sign')
|
|
attr.signs[key] = {
|
|
isWall,
|
|
isHanging,
|
|
rotation: isWall ? facingRotationMap[props.facing] : +props.rotation
|
|
}
|
|
} else if (block.name === 'player_head' || block.name === 'player_wall_head') {
|
|
const key = `${cursor.x},${cursor.y},${cursor.z}`
|
|
const props: any = block.getProperties()
|
|
const facingRotationMap = {
|
|
'north': 0,
|
|
'south': 2,
|
|
'west': 3,
|
|
'east': 1
|
|
}
|
|
const isWall = block.name === 'player_wall_head'
|
|
attr.heads[key] = {
|
|
isWall,
|
|
rotation: isWall ? facingRotationMap[props.facing] : +props.rotation
|
|
}
|
|
}
|
|
const biome = block.biome.name
|
|
|
|
if (world.preflat) { // 10% perf
|
|
const patchProperties = preflatBlockCalculation(block, world, cursor)
|
|
if (patchProperties) {
|
|
block._originalProperties ??= block._properties
|
|
block._properties = { ...block._originalProperties, ...patchProperties }
|
|
if (block.models && JSON.stringify(block._originalProperties) !== JSON.stringify(block._properties)) {
|
|
// recompute models
|
|
block.models = undefined
|
|
block = world.getBlock(cursor, blockProvider, attr)!
|
|
}
|
|
} else {
|
|
block._properties = block._originalProperties ?? block._properties
|
|
block._originalProperties = undefined
|
|
}
|
|
}
|
|
|
|
const isWaterlogged = isBlockWaterlogged(block)
|
|
if (block.name === 'water' || isWaterlogged) {
|
|
const pos = cursor.clone()
|
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
delayedRender.push(() => {
|
|
renderLiquid(world, pos, blockProvider.getTextureInfo('water_still'), block.type, biome, true, attr, !isWaterlogged)
|
|
})
|
|
attr.blocksCount++
|
|
} else if (block.name === 'lava') {
|
|
renderLiquid(world, cursor, blockProvider.getTextureInfo('lava_still'), block.type, biome, false, attr, false)
|
|
attr.blocksCount++
|
|
}
|
|
if (block.name !== 'water' && block.name !== 'lava' && !INVISIBLE_BLOCKS.has(block.name)) {
|
|
// cache
|
|
let { models } = block
|
|
|
|
models ??= unknownBlockModel
|
|
|
|
const firstForceVar = world.config.debugModelVariant?.[0]
|
|
let part = 0
|
|
for (const modelVars of models ?? []) {
|
|
const pos = cursor.clone()
|
|
// const variantRuntime = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), modelVars.length)
|
|
const variantRuntime = 0
|
|
const useVariant = world.config.debugModelVariant?.[part] ?? firstForceVar ?? variantRuntime
|
|
part++
|
|
const model = modelVars[useVariant] ?? modelVars[0]
|
|
if (!model) continue
|
|
|
|
// #region 10%
|
|
let globalMatrix = null as any
|
|
let globalShift = null as any
|
|
for (const axis of ['x', 'y', 'z'] as const) {
|
|
if (axis in model) {
|
|
globalMatrix = globalMatrix ?
|
|
matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0))) :
|
|
buildRotationMatrix(axis, -(model[axis] ?? 0))
|
|
}
|
|
}
|
|
if (globalMatrix) {
|
|
globalShift = [8, 8, 8]
|
|
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
|
|
}
|
|
// #endregion
|
|
|
|
for (const element of model.elements ?? []) {
|
|
const ao = model.ao ?? true
|
|
if (block.transparent) {
|
|
const pos = cursor.clone()
|
|
delayedRender.push(() => {
|
|
renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome)
|
|
})
|
|
} else {
|
|
// 60%
|
|
renderElement(world, cursor, element, ao, attr, globalMatrix, globalShift, block, biome)
|
|
}
|
|
}
|
|
}
|
|
if (part > 0) attr.blocksCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const render of delayedRender) {
|
|
render()
|
|
}
|
|
delayedRender = []
|
|
|
|
let ndx = attr.positions.length / 3
|
|
for (let i = 0; i < attr.t_positions!.length / 12; i++) {
|
|
attr.indices[attr.indicesCount++] = ndx
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
attr.indices[attr.indicesCount++] = ndx + 3
|
|
// back face
|
|
attr.indices[attr.indicesCount++] = ndx
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
attr.indices[attr.indicesCount++] = ndx + 2
|
|
attr.indices[attr.indicesCount++] = ndx + 3
|
|
attr.indices[attr.indicesCount++] = ndx + 1
|
|
ndx += 4
|
|
}
|
|
|
|
attr.positions.push(...attr.t_positions!)
|
|
attr.normals.push(...attr.t_normals!)
|
|
attr.colors.push(...attr.t_colors!)
|
|
attr.uvs.push(...attr.t_uvs!)
|
|
|
|
delete attr.t_positions
|
|
delete attr.t_normals
|
|
delete attr.t_colors
|
|
delete attr.t_uvs
|
|
|
|
attr.positions = new Float32Array(attr.positions) as any
|
|
attr.normals = new Float32Array(attr.normals) as any
|
|
attr.colors = new Float32Array(attr.colors) as any
|
|
attr.uvs = new Float32Array(attr.uvs) as any
|
|
attr.using32Array = arrayNeedsUint32(attr.indices)
|
|
if (attr.using32Array) {
|
|
attr.indices = new Uint32Array(attr.indices)
|
|
} else {
|
|
attr.indices = new Uint16Array(attr.indices)
|
|
}
|
|
|
|
if (needTiles) {
|
|
delete attr.positions
|
|
delete attr.normals
|
|
delete attr.colors
|
|
delete attr.uvs
|
|
}
|
|
|
|
return attr
|
|
}
|
|
|
|
// copied from three.js
|
|
function arrayNeedsUint32 (array) {
|
|
|
|
// assumes larger values usually on last
|
|
|
|
for (let i = array.length - 1; i >= 0; -- i) {
|
|
|
|
if (array[i] >= 65_535) return true // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
export const setBlockStatesData = (blockstatesModels, blocksAtlas: any, _needTiles = false, useUnknownBlockModel = true, version = 'latest') => {
|
|
blockProvider = worldBlockProvider(blockstatesModels, blocksAtlas, version)
|
|
globalThis.blockProvider = blockProvider
|
|
if (useUnknownBlockModel) {
|
|
unknownBlockModel = blockProvider.getAllResolvedModels0_1({ name: 'unknown', properties: {} })
|
|
}
|
|
|
|
needTiles = _needTiles
|
|
}
|