Skip to content

Commit

Permalink
feat: revamp item renderer so it can now return full model for blocks…
Browse files Browse the repository at this point in the history
… and it its flexible to model resolution now
  • Loading branch information
zardoy committed Feb 7, 2025
1 parent eb47ea4 commit 37ec050
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 69 deletions.
9 changes: 8 additions & 1 deletion src/consumer/assetsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,19 @@ export class AssetsParser {
return modelsResolved // todo figure out the type error
}

private getResolvedModelsByModel(model: string, debugQueryName?: string, clearModel = true) {
public getResolvedModelsByModel(model: string, debugQueryName?: string, clearModel = true) {
if (clearModel) {
this.resolvedModel = {}
}
const modelData = this.blockModelsStore.get(this.version, model)
if (!modelData) return
return this.getResolvedModelsByModelData(modelData, debugQueryName, clearModel)
}

public getResolvedModelsByModelData(modelData: BlockModel, debugQueryName?: string, clearModel = true) {
if (clearModel) {
this.resolvedModel = {}
}
const collectedParentModels = [] as BlockModel[]
const collectModels = (model: BlockModel) => {
collectedParentModels.push(model)
Expand Down
21 changes: 17 additions & 4 deletions src/consumer/itemsRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ describe('ItemsRenderer', () => {
const blocksAtlasParser = new AtlasParser(blocksAtlases, '')
const renderer = new ItemsRenderer('latest', blockstatesModels, itemsAtlasParser, blocksAtlasParser)

const getItemTexture = (item: string) => {
const result = renderer.getItemTexture(item)
if (!result) return result
result['resolvedModel'] = !!result['resolvedModel']
return result
}

describe('getItemTexture', () => {
it('items texture', () => {
expect(renderer.getItemTexture('item_frame')).toMatchInlineSnapshot(`
expect(getItemTexture('item_frame')).toMatchInlineSnapshot(`
{
"path": "items",
"resolvedModel": false,
"slice": [
720,
128,
Expand All @@ -30,7 +38,7 @@ describe('ItemsRenderer', () => {
})

it('full blocks texture', () => {
expect(renderer.getItemTexture('stone')).toMatchInlineSnapshot(`
expect(getItemTexture('stone')).toMatchInlineSnapshot(`
{
"left": {
"path": "blocks",
Expand All @@ -42,6 +50,7 @@ describe('ItemsRenderer', () => {
],
"type": "blocks",
},
"resolvedModel": true,
"right": {
"path": "blocks",
"slice": [
Expand All @@ -67,9 +76,10 @@ describe('ItemsRenderer', () => {
})

it('invsprite textures', () => {
expect(renderer.getItemTexture('chest')).toMatchInlineSnapshot(`
expect(getItemTexture('chest')).toMatchInlineSnapshot(`
{
"path": "items",
"resolvedModel": false,
"slice": [
400,
0,
Expand All @@ -82,7 +92,8 @@ describe('ItemsRenderer', () => {
})

it('not implemented logic', () => {
expect(renderer.getItemTexture('cut_copper_slab')).toMatchInlineSnapshot(`undefined`)
expect(getItemTexture('cut_copper_slab')).toMatchInlineSnapshot(`undefined`)
expect(getItemTexture('bla_bla')).toMatchInlineSnapshot(`undefined`)
})
})

Expand All @@ -100,6 +111,8 @@ describe('ItemsRenderer', () => {
"type": "items",
}
`)

expect(renderer.resolveTexture('bla')).toMatchInlineSnapshot(`undefined`)
})
})
})
38 changes: 23 additions & 15 deletions src/consumer/itemsRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AtlasParser } from './atlasParser';
import { AssetsParser } from './assetsParser';
import { BlockModelsStore, BlockStatesStore, getLoadedBlockstatesStore, getLoadedModelsStore } from './stores';
import { ItemModel } from './types';
import { BlockModel, ItemModel } from './types';

type TextureSlice = [absoluteX: number, absoluteY: number, width: number, height: number]

Expand Down Expand Up @@ -35,13 +35,9 @@ export class ItemsRenderer {
}
}

tryGetFullBlock(blockName: string, properties: Record<string, string | boolean> = {}) {
tryGetFullBlock(model: any, blockName: string) {
if (!this.blocksAtlasParser) return
const resolvedModelParts = this.assetsParser.getResolvedModelFirst({
name: blockName,
properties,
}, true)
const resolvedModel = resolvedModelParts?.[0]
const { resolvedModel } = this.assetsParser.getResolvedModelsByModelData(model)
if (!resolvedModel?.elements?.length) return
const isAllFullModels = resolvedModel.elements.every(elem => elem.from[0] === 0 && elem.from[1] === 0 && elem.from[2] === 0 && elem.to[0] === 16 && elem.to[1] === 16 && elem.to[2] === 16)
if (!isAllFullModels) return
Expand All @@ -60,22 +56,34 @@ export class ItemsRenderer {
top: topTextureResolved,
left: leftTextureResolved,
right: rightTextureResolved,
resolvedModel: resolvedModel as BlockModel
}
}

getItemTexture(itemNameOrModel: string, properties: Record<string, string | boolean> = {}, exactItemResolve = false) {
itemNameOrModel = itemNameOrModel.replace(/^minecraft:/, '')
getItemTexture(itemNameOrModel: string, _properties: Record<string, string | boolean> = {}, exactItemResolve = false) {
let [_namespace, _name] = itemNameOrModel.includes(':') ? itemNameOrModel.split(':') : ['minecraft', itemNameOrModel]
const namespace = _namespace === 'minecraft' ? '' : _namespace!
const name = _name!
const itemModelPath = namespace ? `${namespace}:item/${name}` : `item/${name}`
const blockModelPath = namespace ? `${namespace}:block/${name}` : `block/${name}`
const cleanFullModelPath = namespace ? `${namespace}:${name}` : name

let model: ItemModel | undefined
if (itemNameOrModel.includes('/') || exactItemResolve) {
model = this.modelsStore.get(this.version, itemNameOrModel)
if (cleanFullModelPath.includes('/') || exactItemResolve) {
model = this.modelsStore.get(this.version, cleanFullModelPath)
} else {
model = this.modelsStore.get(this.version, `item/${itemNameOrModel}`)
if (!model || model.parent?.includes('block/')) {
return this.tryGetFullBlock(itemNameOrModel, properties)
model = this.modelsStore.get(this.version, itemModelPath)
let blockModel = model?.parent?.includes('block/')
if (!model) {
model = this.modelsStore.get(this.version, blockModelPath)
if (model) blockModel = true
}
if (blockModel) {
return this.tryGetFullBlock(model, cleanFullModelPath)
}
}
if (!model) return
const texture = itemNameOrModel.includes('block/') ?
const texture = cleanFullModelPath.includes('block/') ?
// first defined block texture
Object.values(model.textures ?? {})[0] :
model.textures?.layer0 // classic item texture
Expand Down
109 changes: 60 additions & 49 deletions src/consumer/worldBlockProvider.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,70 @@
import { AtlasParser } from './atlasParser'
import { AssetsParser, QueriedBlock } from './assetsParser'
import { getLoadedBlockstatesStore, getLoadedModelsStore } from './stores'
import { BlockModel } from './types'

export default function worldBlockProvider(blockstatesModels: any, blocksAtlas: any, version: string) {
const blockStatesStore = getLoadedBlockstatesStore(blockstatesModels)
const blockModelsStore = getLoadedModelsStore(blockstatesModels)

const assetsParser = new AssetsParser(version, blockStatesStore, blockModelsStore)
const atlasParser = new AtlasParser(blocksAtlas, 'latest', 'legacy')

type ResolveModelReturnType = NonNullable<ReturnType<typeof assetsParser.getAllResolvedModels>>

const getTextureInfo = (textureName: string) => {
return atlasParser.getTextureInfo(textureName.replace('block/', '').replace('blocks/', ''), version)
}

const transformModel = (model: BlockModel | ResolveModelReturnType[number][number], block: Omit<QueriedBlock, 'stateId'>) => {
const { elements, textures, ...rest } = model
return {
// todo validate elements
elements: elements!?.map((elem) => {
return {
...elem,
faces: Object.fromEntries(Object.entries(elem.faces).map(([faceName, face]) => {
const texture = face.texture
if (!texture) throw new Error(`Missing resolved texture ${texture} for face ${faceName} of ${block.name}`)
const finalTexture = getTextureInfo(texture)
if (!finalTexture) throw new Error(`Missing texture data ${texture} for ${block.name}`)

const _from = elem.from
const _to = elem.to
// taken from https://github.com/DragonDev1906/Minecraft-Overviewer/
const COORDINATE_MAX = 16
const uv = (face.uv || {
// default UVs
// format: [u1, v1, u2, v2] (u = x, v = y)
north: [_to[0], COORDINATE_MAX - _to[1], _from[0], COORDINATE_MAX - _from[1]],
east: [_from[2], COORDINATE_MAX - _to[1], _to[2], COORDINATE_MAX - _from[1]],
south: [_from[0], COORDINATE_MAX - _to[1], _to[0], COORDINATE_MAX - _from[1]],
west: [_from[2], COORDINATE_MAX - _to[1], _to[2], COORDINATE_MAX - _from[1]],
up: [_from[0], _from[2], _to[0], _to[2]],
down: [_to[0], _from[2], _from[0], _to[2]]
}[faceName]!) as [number, number, number, number]

const su = (uv[2] - uv[0]) / COORDINATE_MAX * finalTexture.su
const sv = (uv[3] - uv[1]) / COORDINATE_MAX * finalTexture.sv

return [faceName, {
...face,
texture: {
u: finalTexture.u + uv[0] / 16 * finalTexture.su,
v: finalTexture.v + uv[1] / 16 * finalTexture.sv,
su,
sv,
tileIndex: finalTexture.tileIndex,
debugName: texture,
},
}]
}))
}
}),
...rest
}
}

return {
getAllResolvedModels0_1(block: Omit<QueriedBlock, 'stateId'>, fallbackVariant = false) {
const modelsParts = assetsParser.getAllResolvedModels(block, fallbackVariant) ?? []
Expand All @@ -16,58 +73,12 @@ export default function worldBlockProvider(blockstatesModels: any, blocksAtlas:

return modelsParts.map(modelVariants => {
return modelVariants.map(model => {
const { elements, textures, ...rest } = model
return {
// todo validate elements
elements: elements!?.map((elem) => {
return {
...elem,
faces: Object.fromEntries(Object.entries(elem.faces).map(([faceName, face]) => {
const texture = face.texture
if (!texture) throw new Error(`Missing resolved texture ${texture} for face ${faceName} of ${block.name}`)
const finalTexture = this.getTextureInfo(texture)
if (!finalTexture) throw new Error(`Missing texture data ${texture} for ${block.name}`)

const _from = elem.from
const _to = elem.to
// taken from https://github.com/DragonDev1906/Minecraft-Overviewer/
const COORDINATE_MAX = 16
const uv = (face.uv || {
// default UVs
// format: [u1, v1, u2, v2] (u = x, v = y)
north: [_to[0], COORDINATE_MAX - _to[1], _from[0], COORDINATE_MAX - _from[1]],
east: [_from[2], COORDINATE_MAX - _to[1], _to[2], COORDINATE_MAX - _from[1]],
south: [_from[0], COORDINATE_MAX - _to[1], _to[0], COORDINATE_MAX - _from[1]],
west: [_from[2], COORDINATE_MAX - _to[1], _to[2], COORDINATE_MAX - _from[1]],
up: [_from[0], _from[2], _to[0], _to[2]],
down: [_to[0], _from[2], _from[0], _to[2]]
}[faceName]!) as [number, number, number, number]

const su = (uv[2] - uv[0]) / COORDINATE_MAX * finalTexture.su
const sv = (uv[3] - uv[1]) / COORDINATE_MAX * finalTexture.sv

return [faceName, {
...face,
texture: {
u: finalTexture.u + uv[0] / 16 * finalTexture.su,
v: finalTexture.v + uv[1] / 16 * finalTexture.sv,
su,
sv,
tileIndex: finalTexture.tileIndex,
debugName: texture,
},
}]
}))
}
}),
...rest
}
return transformModel(model, block);
}).filter(a => a.elements?.length)
}).filter(a => a.length)
},
getTextureInfo(textureName: string) {
return atlasParser.getTextureInfo(textureName.replace('block/', '').replace('blocks/', ''), version)
}
transformModel,
getTextureInfo
}
}

Expand Down

0 comments on commit 37ec050

Please sign in to comment.