pages235/prismarine-viewer/viewer/prepare/modelsBuilder.ts
Wolf2323 38e4efc79b
fix: improve signs models & text, added all missing sign types (#143)
Co-authored-by: Vitaly Turovsky <vital2580@icloud.com>
2024-06-11 21:14:58 +03:00

259 lines
6.4 KiB
TypeScript

type ModelBasic = {
model: string
x?: number
y?: number
uvlock?: boolean
}
type BlockApplyModel = ModelBasic | (ModelBasic & { weight })[]
type BlockStateCondition = {
[name: string]: string | number
}
type BlockState = {
variants?: {
[name: string | ""]: BlockApplyModel
}
multipart?: {
when: {
[name: string]: string | number
} & {
OR?: BlockStateCondition[]
}
apply: BlockApplyModel
}[]
}
type BlockModel = {
parent?: string
textures?: {
[name: string]: string
}
elements?: {
from: number[]
to: number[]
faces: {
[name: string]: {
texture: string
uv?: number[]
cullface?: string
}
}
}[]
ambientocclusion?: boolean
x?: number
y?: number
z?: number
ao?: boolean
}
export type McAssets = {
blocksStates: {
[x: string]: BlockState
}
blocksModels: {
[x: string]: BlockModel
}
directory: string
version: string
}
export type BlockStatesOutput = {
// states: {
[blockName: string]: any/* ResolvedModel */
// }
// defaults: {
// su: number
// sv: number
// }
}
export type ResolvedModel = {
textures: {
[name: string]: {
u: number
v: number
su: number
sv: number
bu: number
bv: number
}
}
elements: {
from: number[]
to: number[]
faces: {
[name: string]: {
texture: {
u: number
v: number
su: number
sv: number
bu: number
bv: number
}
}
}
}[]
ao: boolean
x?: number
y?: number
z?: number
}
export const addBlockAllModel = (mcAssets: McAssets, name: string, texture = name) => {
mcAssets.blocksStates[name] = {
"variants": {
"": {
"model": name
}
}
}
mcAssets.blocksModels[name] = {
"parent": "block/cube_all",
"textures": {
"all": `blocks/${texture}`
}
}
}
function cleanupBlockName (name: string) {
if (name.startsWith('block') || name.startsWith('minecraft:block')) return name.split('/')[1]
return name
}
const objectAssignStrict = <T extends Record<string, any>> (target: T, source: Partial<T>) => Object.assign(target, source)
function getFinalModel (name: string, blocksModels: { [x: string]: BlockModel }) {
name = cleanupBlockName(name)
const input = blocksModels[name]
if (!input) {
return null
}
let out: BlockModel | null = {
textures: {},
elements: [],
ao: true,
x: input.x,
y: input.y,
z: input.z,
}
if (input.parent) {
out = getFinalModel(input.parent, blocksModels)
if (!out) return null
}
if (input.textures) {
Object.assign(out.textures!, deepCopy(input.textures))
}
if (input.elements) out.elements = deepCopy(input.elements)
if (input.ao !== undefined) out.ao = input.ao
return out
}
const deepCopy = (obj) => JSON.parse(JSON.stringify(obj))
const workerUsedTextures = ['particle']
function prepareModel (model: BlockModel, texturesJson) {
const newModel = {}
const getFinalTexture = (originalBlockName) => {
// texture name e.g. blocks/anvil_base
const cleanBlockName = cleanupBlockName(originalBlockName)
return { ...texturesJson[cleanBlockName], /* __debugName: cleanBlockName */ }
}
const finalTextures = []
// resolve texture names eg west: #all -> blocks/stone
for (const side in model.textures) {
let texture = model.textures[side]
while (texture.charAt(0) === '#') {
const textureName = texture.slice(1)
texture = model.textures[textureName]
if (texture === undefined) throw new Error(`Texture ${textureName} in ${JSON.stringify(model.textures)} not found`)
}
finalTextures[side] = getFinalTexture(texture)
if (workerUsedTextures.includes(side)) {
model.textures[side] = finalTextures[side]
}
}
for (const elem of model.elements!) {
for (const sideName of Object.keys(elem.faces)) {
const face = elem.faces[sideName]
const textureRaw = face.texture.charAt(0) === '#'
? finalTextures![face.texture.slice(1)]
: getFinalTexture(face.texture)
if (!textureRaw) throw new Error(`Texture ${face.texture} in ${JSON.stringify(model.textures)} not found`)
const finalTexture = deepCopy(
textureRaw
)
const _from = elem.from
const _to = elem.to
// taken from https://github.com/DragonDev1906/Minecraft-Overviewer/
const uv = face.uv || {
// default UVs
// format: [u1, v1, u2, v2] (u = x, v = y)
north: [_to[0], 16 - _to[1], _from[0], 16 - _from[1]],
east: [_from[2], 16 - _to[1], _to[2], 16 - _from[1]],
south: [_from[0], 16 - _to[1], _to[0], 16 - _from[1]],
west: [_from[2], 16 - _to[1], _to[2], 16 - _from[1]],
up: [_from[0], _from[2], _to[0], _to[2]],
down: [_to[0], _from[2], _from[0], _to[2]]
}[sideName]!
const su = (uv[2] - uv[0]) / 16 * finalTexture.su
const sv = (uv[3] - uv[1]) / 16 * finalTexture.sv
finalTexture.u += uv[0] / 16 * finalTexture.su
finalTexture.v += uv[1] / 16 * finalTexture.sv
finalTexture.su = su
finalTexture.sv = sv
face.texture = finalTexture
}
}
return model
}
function resolveModel (name, blocksModels, texturesJson) {
const model = getFinalModel(name, blocksModels)
return prepareModel(model, texturesJson.textures)
}
export function prepareBlocksStates (mcAssets: McAssets, atlas: { json: any }) {
addBlockAllModel(mcAssets, 'missing_texture')
const blocksStates = mcAssets.blocksStates
for (const block of Object.values(blocksStates)) {
if (!block) continue
if (block.variants) {
for (const variant of Object.values(block.variants)) {
if (variant instanceof Array) {
for (const v of variant) {
v.model = resolveModel(v.model, mcAssets.blocksModels, atlas.json) as any
}
} else {
variant.model = resolveModel(variant.model, mcAssets.blocksModels, atlas.json) as any
}
}
}
if (block.multipart) {
for (const variant of block.multipart) {
if (variant.apply instanceof Array) {
for (const v of variant.apply) {
v.model = resolveModel(v.model, mcAssets.blocksModels, atlas.json) as any
}
} else {
variant.apply.model = resolveModel(variant.apply.model, mcAssets.blocksModels, atlas.json) as any
}
}
}
}
return blocksStates
}