508 lines
14 KiB
JavaScript
508 lines
14 KiB
JavaScript
const { Vec3 } = require('vec3')
|
|
|
|
const tints = require('minecraft-data')('1.16.2').tints
|
|
|
|
for (const key of Object.keys(tints)) {
|
|
tints[key] = prepareTints(tints[key])
|
|
}
|
|
|
|
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
|
|
}
|
|
})
|
|
}
|
|
|
|
function tintToGl (tint) {
|
|
const r = (tint >> 16) & 0xff
|
|
const g = (tint >> 8) & 0xff
|
|
const b = tint & 0xff
|
|
return [r / 255, g / 255, b / 255]
|
|
}
|
|
|
|
const elemFaces = {
|
|
up: {
|
|
dir: [0, 1, 0],
|
|
mask1: [1, 1, 0],
|
|
mask2: [0, 1, 1],
|
|
corners: [
|
|
[0, 1, 1, 0, 1],
|
|
[1, 1, 1, 1, 1],
|
|
[0, 1, 0, 0, 0],
|
|
[1, 1, 0, 1, 0]
|
|
]
|
|
},
|
|
down: {
|
|
dir: [0, -1, 0],
|
|
mask1: [1, 1, 0],
|
|
mask2: [0, 1, 1],
|
|
corners: [
|
|
[1, 0, 1, 0, 1],
|
|
[0, 0, 1, 1, 1],
|
|
[1, 0, 0, 0, 0],
|
|
[0, 0, 0, 1, 0]
|
|
]
|
|
},
|
|
east: {
|
|
dir: [1, 0, 0],
|
|
mask1: [1, 1, 0],
|
|
mask2: [1, 0, 1],
|
|
corners: [
|
|
[1, 1, 1, 0, 0],
|
|
[1, 0, 1, 0, 1],
|
|
[1, 1, 0, 1, 0],
|
|
[1, 0, 0, 1, 1]
|
|
]
|
|
},
|
|
west: {
|
|
dir: [-1, 0, 0],
|
|
mask1: [1, 1, 0],
|
|
mask2: [1, 0, 1],
|
|
corners: [
|
|
[0, 1, 0, 0, 0],
|
|
[0, 0, 0, 0, 1],
|
|
[0, 1, 1, 1, 0],
|
|
[0, 0, 1, 1, 1]
|
|
]
|
|
},
|
|
north: {
|
|
dir: [0, 0, -1],
|
|
mask1: [1, 0, 1],
|
|
mask2: [0, 1, 1],
|
|
corners: [
|
|
[1, 0, 0, 0, 1],
|
|
[0, 0, 0, 1, 1],
|
|
[1, 1, 0, 0, 0],
|
|
[0, 1, 0, 1, 0]
|
|
]
|
|
},
|
|
south: {
|
|
dir: [0, 0, 1],
|
|
mask1: [1, 0, 1],
|
|
mask2: [0, 1, 1],
|
|
corners: [
|
|
[0, 0, 1, 0, 1],
|
|
[1, 0, 1, 1, 1],
|
|
[0, 1, 1, 0, 0],
|
|
[1, 1, 1, 1, 0]
|
|
]
|
|
}
|
|
}
|
|
|
|
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))
|
|
if (blockAbove && blockAbove.type === type) return 1
|
|
return 8 / 9
|
|
}
|
|
return ((block.metadata >= 8 ? 8 : 7 - block.metadata) + 1) / 9
|
|
}
|
|
|
|
function renderLiquid (world, cursor, texture, type, biome, water, attr) {
|
|
const heights = []
|
|
for (let z = -1; z <= 1; z++) {
|
|
for (let x = -1; x <= 1; x++) {
|
|
heights.push(getLiquidRenderHeight(world, world.getBlock(cursor.offset(x, 0, z)), type))
|
|
}
|
|
}
|
|
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]))
|
|
]
|
|
|
|
for (const face in elemFaces) {
|
|
const { dir, corners } = elemFaces[face]
|
|
const isUp = dir[1] === 1
|
|
|
|
const neighbor = world.getBlock(cursor.offset(...dir))
|
|
if (!neighbor) continue
|
|
if (neighbor.type === type) continue
|
|
if ((neighbor.isCube && !isUp) || neighbor.material === 'plant' || neighbor.getProperties().waterlogged) continue
|
|
if (neighbor.position.y < 0) 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]
|
|
}
|
|
|
|
const u = texture.u
|
|
const v = texture.v
|
|
const su = texture.su
|
|
const sv = texture.sv
|
|
|
|
for (const pos of corners) {
|
|
const height = cornerHeights[pos[2] * 2 + pos[0]]
|
|
attr.t_positions.push(
|
|
(pos[0] ? 1 : 0) + (cursor.x & 15) - 8,
|
|
(pos[1] ? height : 0) + (cursor.y & 15) - 8,
|
|
(pos[2] ? 1 : 0) + (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)
|
|
attr.t_colors.push(tint[0], tint[1], tint[2])
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (!b) return a
|
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
|
}
|
|
|
|
function matmul3 (matrix, vector) {
|
|
if (!matrix) return vector
|
|
return [
|
|
matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
|
|
matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
|
|
matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2]
|
|
]
|
|
}
|
|
|
|
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]
|
|
const a21 = a[0][1]; const a22 = a[1][1]; const a23 = a[2][1]
|
|
const a31 = a[0][2]; const a32 = a[1][2]; const a33 = a[2][2]
|
|
|
|
const b11 = b[0][0]; const b12 = b[1][0]; const b13 = b[2][0]
|
|
const b21 = b[0][1]; const b22 = b[1][1]; const b23 = b[2][1]
|
|
const b31 = b[0][2]; const b32 = b[1][2]; const b33 = b[2][2]
|
|
|
|
te[0][0] = a11 * b11 + a12 * b21 + a13 * b31
|
|
te[1][0] = a11 * b12 + a12 * b22 + a13 * b32
|
|
te[2][0] = a11 * b13 + a12 * b23 + a13 * b33
|
|
|
|
te[0][1] = a21 * b11 + a22 * b21 + a23 * b31
|
|
te[1][1] = a21 * b12 + a22 * b22 + a23 * b32
|
|
te[2][1] = a21 * b13 + a22 * b23 + a23 * b33
|
|
|
|
te[0][2] = a31 * b11 + a32 * b21 + a33 * b31
|
|
te[1][2] = a31 * b12 + a32 * b22 + a33 * b32
|
|
te[2][2] = a31 * b13 + a32 * b23 + a33 * b33
|
|
|
|
return te
|
|
}
|
|
|
|
function buildRotationMatrix (axis, degree) {
|
|
const radians = degree / 180 * Math.PI
|
|
const cos = Math.cos(radians)
|
|
const sin = Math.sin(radians)
|
|
|
|
const axis0 = { x: 0, y: 1, z: 2 }[axis]
|
|
const axis1 = (axis0 + 1) % 3
|
|
const axis2 = (axis0 + 2) % 3
|
|
|
|
const matrix = [
|
|
[0, 0, 0],
|
|
[0, 0, 0],
|
|
[0, 0, 0]
|
|
]
|
|
|
|
matrix[axis0][axis0] = 1
|
|
matrix[axis1][axis1] = cos
|
|
matrix[axis1][axis2] = -sin
|
|
matrix[axis2][axis1] = +sin
|
|
matrix[axis2][axis2] = cos
|
|
|
|
return matrix
|
|
}
|
|
|
|
function renderElement (world, cursor, element, doAO, attr, globalMatrix, globalShift, 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) continue
|
|
if (cullIfIdentical && neighbor.type === block.type) continue
|
|
if (!neighbor.transparent && neighbor.isCube) continue
|
|
if (neighbor.position.y < 0) 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
|
|
let localShift = null
|
|
|
|
if (element.rotation) {
|
|
localMatrix = buildRotationMatrix(
|
|
element.rotation.axis,
|
|
element.rotation.angle
|
|
)
|
|
|
|
localShift = vecsub3(
|
|
element.rotation.origin,
|
|
matmul3(
|
|
localMatrix,
|
|
element.rotation.origin
|
|
)
|
|
)
|
|
}
|
|
|
|
const aos = []
|
|
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 = (side1 && side1.isCube) ? 1 : 0
|
|
const side2Block = (side2 && side2.isCube) ? 1 : 0
|
|
const cornerBlock = (corner && corner.isCube) ? 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 getSectionGeometry (sx, sy, sz, world, blocksStates) {
|
|
const attr = {
|
|
sx: sx + 8,
|
|
sy: sy + 8,
|
|
sz: sz + 8,
|
|
positions: [],
|
|
normals: [],
|
|
colors: [],
|
|
uvs: [],
|
|
t_positions: [],
|
|
t_normals: [],
|
|
t_colors: [],
|
|
t_uvs: [],
|
|
indices: []
|
|
}
|
|
|
|
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++) {
|
|
const block = world.getBlock(cursor)
|
|
const biome = block.biome.name
|
|
if (block.variant === undefined) {
|
|
block.variant = getModelVariants(block, blocksStates)
|
|
}
|
|
|
|
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)
|
|
} else {
|
|
let globalMatrix = null
|
|
let globalShift = null
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let ndx = attr.positions.length / 3
|
|
for (let i = 0; i < attr.t_positions.length / 12; i++) {
|
|
attr.indices.push(
|
|
ndx, ndx + 1, ndx + 2,
|
|
ndx + 2, ndx + 1, ndx + 3,
|
|
// back face
|
|
ndx, ndx + 2, ndx + 1,
|
|
ndx + 2, ndx + 3, 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)
|
|
attr.normals = new Float32Array(attr.normals)
|
|
attr.colors = new Float32Array(attr.colors)
|
|
attr.uvs = new Float32Array(attr.uvs)
|
|
|
|
return attr
|
|
}
|
|
|
|
function parseProperties (properties) {
|
|
if (typeof properties === 'object') { return properties }
|
|
|
|
const json = {}
|
|
for (const prop of properties.split(',')) {
|
|
const [key, value] = prop.split('=')
|
|
json[key] = value
|
|
}
|
|
return json
|
|
}
|
|
|
|
function matchProperties (block, properties) {
|
|
if (!properties) { return true }
|
|
|
|
properties = parseProperties(properties)
|
|
const blockProps = block.getProperties()
|
|
if (properties.OR) {
|
|
return properties.OR.some((or) => matchProperties(block, or))
|
|
}
|
|
for (const prop in blockProps) {
|
|
if (typeof properties[prop] === 'string' && !properties[prop].split('|').some((value) => value === blockProps[prop] + '')) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
function getModelVariants (block, blockStates) {
|
|
if (block.name === '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)) {
|
|
if (!matchProperties(block, properties)) continue
|
|
if (variant instanceof Array) return [variant[0]]
|
|
return [variant]
|
|
}
|
|
}
|
|
if (state.multipart) {
|
|
const parts = state.multipart.filter(multipart => matchProperties(block, multipart.when))
|
|
let variants = []
|
|
for (const part of parts) {
|
|
if (part.apply instanceof Array) {
|
|
variants = [...variants, ...part.apply]
|
|
} else {
|
|
variants = [...variants, part.apply]
|
|
}
|
|
}
|
|
|
|
return variants
|
|
}
|
|
|
|
return []
|
|
}
|
|
|
|
module.exports = { getSectionGeometry }
|