Skip to content

Commit 8774cb2

Browse files
authored
Item renderer update (#41)
* implement default components and component removal * start update item renderer * implement range dispatch * refactor Color to its own namespace, add fromJson * implement item tints * fix imports * update demo and fixes * fix circular dependencies * add item component string parser * don't error on missing item_model component & map color fix * fix circular dependencies * start special renderer * update from 24w46a changed default component handling again * fix imports * store id in itemstack * implement most special models * improve item registry * implement bundle/selected_item * implement bundle/fullness * remove local_time, fix chest special renderer * minor fixes * add tests * fix defaults of properties and tints * add more tests and a few fixes * undo unnecessary formatting changes * update item_definition url * add changes from 1.21.4-pre1
1 parent c38265e commit 8774cb2

22 files changed

+2185
-1045
lines changed

demo/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
input {
2121
padding: 4px;
2222
margin-left: 8px;
23+
width: 500px;
2324
}
2425
.invalid {
2526
color: #cb0000;

demo/main.ts

+33-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { mat4 } from 'gl-matrix'
2-
import type { Resources, Voxel } from '../src/index.js'
3-
import { BlockDefinition, BlockModel, Identifier, ItemRenderer, ItemStack, NormalNoise, Structure, StructureRenderer, TextureAtlas, upperPowerOfTwo, VoxelRenderer, XoroshiroRandom } from '../src/index.js'
2+
import type { ItemRendererResources, ItemRenderingContext, NbtTag, Resources, Voxel } from '../src/index.js'
3+
import { BlockDefinition, BlockModel, Identifier, Item, ItemRenderer, ItemStack, NormalNoise, Structure, StructureRenderer, TextureAtlas, VoxelRenderer, XoroshiroRandom, jsonToNbt, upperPowerOfTwo } from '../src/index.js'
4+
import { } from '../src/nbt/Util.js'
5+
import { ItemModel } from '../src/render/ItemModel.js'
46

57

68
class InteractiveCanvas {
@@ -66,14 +68,16 @@ Promise.all([
6668
fetch(`${MCMETA}registries/item/data.min.json`).then(r => r.json()),
6769
fetch(`${MCMETA}summary/assets/block_definition/data.min.json`).then(r => r.json()),
6870
fetch(`${MCMETA}summary/assets/model/data.min.json`).then(r => r.json()),
71+
fetch(`${MCMETA}summary/assets/item_definition/data.min.json`).then(r => r.json()),
72+
fetch(`${MCMETA}summary/item_components/data.min.json`).then(r => r.json()),
6973
fetch(`${MCMETA}atlas/all/data.min.json`).then(r => r.json()),
7074
new Promise<HTMLImageElement>(res => {
7175
const image = new Image()
7276
image.onload = () => res(image)
7377
image.crossOrigin = 'Anonymous'
7478
image.src = `${MCMETA}atlas/all/atlas.png`
7579
}),
76-
]).then(([items, blockstates, models, uvMap, atlas]) => {
80+
]).then(([items, blockstates, models, item_models, item_components, uvMap, atlas]) => {
7781

7882
// === Prepare assets for item and structure rendering ===
7983

@@ -97,6 +101,21 @@ Promise.all([
97101
})
98102
Object.values(blockModels).forEach((m: any) => m.flatten({ getBlockModel: id => blockModels[id] }))
99103

104+
105+
const itemModels: Record<string, ItemModel> = {}
106+
Object.keys(item_models).forEach(id => {
107+
itemModels['minecraft:' + id] = ItemModel.fromJson(item_models[id].model)
108+
})
109+
110+
111+
Object.keys(item_components).forEach(id => {
112+
const components = new Map<string, NbtTag>()
113+
Object.keys(item_components[id]).forEach(c_id => {
114+
components.set(c_id, jsonToNbt(item_components[id][c_id]))
115+
})
116+
Item.REGISTRY.register(Identifier.create(id), new Item(components))
117+
})
118+
100119
const atlasCanvas = document.createElement('canvas')
101120
const atlasSize = upperPowerOfTwo(Math.max(atlas.width, atlas.height))
102121
atlasCanvas.width = atlasSize
@@ -112,28 +131,36 @@ Promise.all([
112131
})
113132
const textureAtlas = new TextureAtlas(atlasData, idMap)
114133

115-
const resources: Resources = {
134+
const resources: Resources & ItemRendererResources = {
116135
getBlockDefinition(id) { return blockDefinitions[id.toString()] },
117136
getBlockModel(id) { return blockModels[id.toString()] },
118137
getTextureUV(id) { return textureAtlas.getTextureUV(id) },
119138
getTextureAtlas() { return textureAtlas.getTextureAtlas() },
120139
getBlockFlags(id) { return { opaque: false } },
121140
getBlockProperties(id) { return null },
122141
getDefaultBlockProperties(id) { return null },
142+
getItemModel(id) { return itemModels[id.toString()] },
123143
}
124144

125145
// === Item rendering ===
126146

147+
const context: ItemRenderingContext = {
148+
"bundle/selected_item": 0
149+
}
150+
127151
const itemCanvas = document.getElementById('item-display') as HTMLCanvasElement
128152
const itemGl = itemCanvas.getContext('webgl')!
129153
const itemInput = document.getElementById('item-input') as HTMLInputElement
130154
itemInput.value = localStorage.getItem('deepslate_demo_item') ?? 'stone'
131-
const itemRenderer = new ItemRenderer(itemGl, Identifier.parse(itemInput.value), resources)
155+
const itemStack = ItemStack.fromString(itemInput.value)
156+
const itemRenderer = new ItemRenderer(itemGl, itemStack, resources, context)
132157

133158
itemInput.addEventListener('keyup', () => {
134159
try {
135160
const id = itemInput.value
136-
itemRenderer.setItem(new ItemStack(Identifier.parse(id), 1))
161+
const itemStack = ItemStack.fromString(itemInput.value)
162+
itemGl.clear(itemGl.DEPTH_BUFFER_BIT | itemGl.COLOR_BUFFER_BIT);
163+
itemRenderer.setItem(itemStack, context)
137164
itemRenderer.drawItem()
138165
itemInput.classList.remove('invalid')
139166
localStorage.setItem('deepslate_demo_item', id)

src/core/Effects.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { NbtCompound, NbtTag } from '../nbt/index.js'
22
import { NbtType } from '../nbt/index.js'
3-
import type { Color } from '../util/index.js'
4-
import { intToRgb } from '../util/index.js'
3+
import { Color } from '../util/index.js'
54
import { Identifier } from './Identifier.js'
65

76
export const EFFECT_COLORS = new Map<string, number>([
@@ -139,7 +138,7 @@ export namespace PotionContents {
139138

140139
export function getColor(contents: PotionContents): Color {
141140
if (contents.customColor) {
142-
return intToRgb(contents.customColor)
141+
return Color.intToRgb(contents.customColor)
143142
}
144143
const effects = getAllEffects(contents)
145144
return mixEffectColors(effects)
@@ -162,15 +161,15 @@ export namespace PotionContents {
162161
for (const effect of effects) {
163162
const color = EFFECT_COLORS.get(effect.effect.toString())
164163
if (color === undefined) continue
165-
const rgb = intToRgb(color)
164+
const rgb = Color.intToRgb(color)
166165
const amplifier = effect.amplifier + 1
167166
r += amplifier * rgb[0]
168167
g += amplifier * rgb[1]
169168
b += amplifier * rgb[2]
170169
total += amplifier
171170
}
172171
if (total === 0) {
173-
return intToRgb(-13083194)
172+
return Color.intToRgb(-13083194)
174173
}
175174
r = r / total
176175
g = g / total

src/core/Item.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { NbtTag } from "../nbt/index.js"
2+
import { Identifier } from "./index.js"
3+
import { Registry } from "./Registry.js"
4+
5+
6+
export class Item {
7+
public static REGISTRY = Registry.createAndRegister<Item>('item')
8+
9+
constructor(
10+
public components: Map<string, NbtTag> = new Map(),
11+
) {
12+
}
13+
14+
public getComponent<T>(key: string | Identifier, reader: (tag: NbtTag) => T) {
15+
if (typeof key === 'string') {
16+
key = Identifier.parse(key)
17+
}
18+
const value = this.components.get(key.toString())
19+
if (value) {
20+
return reader(value)
21+
}
22+
return undefined
23+
}
24+
25+
public hasComponent(key: string | Identifier) {
26+
if (typeof key === 'string') {
27+
key = Identifier.parse(key)
28+
}
29+
return this.components.has(key.toString())
30+
}
31+
}

src/core/ItemStack.ts

+70-7
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,46 @@
1+
import { NbtParser } from '../nbt/NbtParser.js'
12
import type { NbtTag } from '../nbt/index.js'
23
import { NbtCompound, NbtInt, NbtString } from '../nbt/index.js'
4+
import { StringReader } from '../util/index.js'
5+
import { Holder } from './Holder.js'
36
import { Identifier } from './Identifier.js'
7+
import { Item } from './Item.js'
48

59
export class ItemStack {
10+
private readonly item: Holder<Item | undefined>
11+
612
constructor(
7-
public id: Identifier,
13+
public readonly id: Identifier,
814
public count: number,
9-
public components: Map<string, NbtTag> = new Map(),
10-
) {}
15+
public readonly components: Map<string, NbtTag> = new Map(),
16+
) {
17+
this.item = Holder.reference(Item.REGISTRY, id, false)
18+
}
1119

12-
public getComponent<T>(key: string | Identifier, reader: (tag: NbtTag) => T) {
20+
public getComponent<T>(key: string | Identifier, reader: (tag: NbtTag) => T, includeDefaultComponents: boolean = true): T | undefined {
1321
if (typeof key === 'string') {
1422
key = Identifier.parse(key)
1523
}
24+
25+
if (this.components.has('!' + key.toString())){
26+
return undefined
27+
}
1628
const value = this.components.get(key.toString())
1729
if (value) {
1830
return reader(value)
1931
}
20-
return undefined
32+
return includeDefaultComponents ? this.item.value()?.getComponent(key, reader) : undefined
2133
}
2234

23-
public hasComponent(key: string | Identifier) {
35+
public hasComponent(key: string | Identifier, includeDefaultComponents: boolean = true): boolean {
2436
if (typeof key === 'string') {
2537
key = Identifier.parse(key)
2638
}
27-
return this.components.has(key.toString())
39+
if (this.components.has('!' + key.toString())){
40+
return false
41+
}
42+
43+
return this.components.has(key.toString()) || (includeDefaultComponents && (this.item.value()?.hasComponent(key) ?? false))
2844
}
2945

3046
public clone(): ItemStack {
@@ -77,6 +93,53 @@ export class ItemStack {
7793
return result
7894
}
7995

96+
public static fromString(string: string) {
97+
const reader = new StringReader(string)
98+
99+
while (reader.canRead() && reader.peek() !== '[') {
100+
reader.skip()
101+
}
102+
const itemId = Identifier.parse(reader.getRead())
103+
if (!reader.canRead()){
104+
return new ItemStack(itemId, 1)
105+
}
106+
107+
const components = new Map<string, NbtTag>()
108+
reader.skip()
109+
if (reader.peek() === ']'){
110+
return new ItemStack(itemId, 1, components)
111+
}
112+
do{
113+
if (reader.peek() === '!'){
114+
reader.skip()
115+
const start = reader.cursor
116+
while (reader.canRead() && reader.peek() !== ']' && reader.peek() !== ',') {
117+
reader.skip()
118+
}
119+
components.set('!' + Identifier.parse(reader.getRead(start)).toString(), new NbtCompound())
120+
} else {
121+
const start = reader.cursor
122+
while (reader.canRead() && reader.peek() !== '=') {
123+
reader.skip()
124+
}
125+
const component = Identifier.parse(reader.getRead(start)).toString()
126+
if (!reader.canRead()) break;
127+
reader.skip()
128+
const tag = NbtParser.readTag(reader)
129+
components.set(component, tag)
130+
}
131+
if (!reader.canRead()) break;
132+
if (reader.peek() === ']'){
133+
return new ItemStack(itemId, 1, components)
134+
}
135+
if (reader.peek() !== ','){
136+
throw new Error('Expected , or ]')
137+
}
138+
reader.skip()
139+
} while (reader.canRead())
140+
throw new Error('Missing closing ]')
141+
}
142+
80143
public toNbt() {
81144
const result = new NbtCompound()
82145
.set('id', new NbtString(this.id.toString()))

src/core/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ export * from './Effects.js'
88
export * from './Holder.js'
99
export * from './HolderSet.js'
1010
export * from './Identifier.js'
11+
export * from './Item.js'
1112
export * from './ItemStack.js'
1213
export * from './PalettedContainer.js'
1314
export * from './Registry.js'
1415
export * from './Rotation.js'
1516
export * from './Structure.js'
1617
export * from './StructureProvider.js'
18+

src/nbt/tags/Util.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { NbtByte, NbtCompound, NbtDouble, NbtInt, NbtList, NbtString, NbtTag } from "./index.js"
2+
3+
export function jsonToNbt(value: unknown): NbtTag {
4+
if (typeof value === 'string') {
5+
return new NbtString(value)
6+
}
7+
if (typeof value === 'number') {
8+
return Number.isInteger(value) ? new NbtInt(value) : new NbtDouble(value)
9+
}
10+
if (typeof value === 'boolean') {
11+
return new NbtByte(value)
12+
}
13+
if (Array.isArray(value)) {
14+
return new NbtList(value.map(jsonToNbt))
15+
}
16+
if (typeof value === 'object' && value !== null) {
17+
return new NbtCompound(
18+
new Map(Object.entries(value ?? {})
19+
.map(([k, v]) => [k, jsonToNbt(v)]))
20+
)
21+
}
22+
return new NbtByte(0)
23+
}

src/nbt/tags/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ export * from './NbtShort.js'
1414
export * from './NbtString.js'
1515
export * from './NbtTag.js'
1616
export * from './NbtType.js'
17+
export * from './Util.js'
18+

src/render/BlockColors.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { clamp } from '../math/index.js'
2-
import type { Color } from '../util/index.js'
3-
import { intToRgb } from '../util/index.js'
2+
import { Color } from '../util/index.js'
43

54
const grass: Color = [124 / 255, 189 / 255, 107 / 255]
6-
const spruce = intToRgb(6396257)
7-
const birch = intToRgb(8431445)
8-
const foliage = intToRgb(4764952)
9-
const water = intToRgb(4159204)
10-
const attached_stem = intToRgb(8431445)
11-
const lily_pad = intToRgb(2129968)
5+
const spruce = Color.intToRgb(6396257)
6+
const birch = Color.intToRgb(8431445)
7+
const foliage = Color.intToRgb(4764952)
8+
const water = Color.intToRgb(4159204)
9+
const attached_stem = Color.intToRgb(8431445)
10+
const lily_pad = Color.intToRgb(2129968)
1211

1312
const redstone = (power: number): Color => {
1413
const a = power / 15

src/render/BlockModel.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { TextureAtlasProvider, UV } from './TextureAtlas.js'
1111

1212
type Axis = 'x' | 'y' | 'z'
1313

14-
type Display = 'thirdperson_righthand' | 'thirdperson_lefthand' | 'firstperson_righthand' | 'firstperson_lefthand' | 'gui' | 'head' | 'ground' | 'fixed'
14+
export type Display = 'thirdperson_righthand' | 'thirdperson_lefthand' | 'firstperson_righthand' | 'firstperson_lefthand' | 'gui' | 'head' | 'ground' | 'fixed' | 'none'
1515

1616
type BlockModelFace = {
1717
texture: string,

0 commit comments

Comments
 (0)