Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item renderer update #41

Merged
merged 27 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
00eded2
implement default components and component removal
jacobsjo Nov 11, 2024
85868ab
start update item renderer
jacobsjo Nov 12, 2024
38ca03a
implement range dispatch
jacobsjo Nov 12, 2024
f7314fc
refactor Color to its own namespace, add fromJson
jacobsjo Nov 12, 2024
5b0eb45
implement item tints
jacobsjo Nov 12, 2024
b005e8a
fix imports
jacobsjo Nov 12, 2024
81cb1fa
update demo and fixes
jacobsjo Nov 12, 2024
de1a8ba
fix circular dependencies
jacobsjo Nov 12, 2024
6180c2c
add item component string parser
jacobsjo Nov 13, 2024
b2ecc70
don't error on missing item_model component & map color fix
jacobsjo Nov 13, 2024
06d0dca
fix circular dependencies
jacobsjo Nov 13, 2024
a6c3f4c
start special renderer
jacobsjo Nov 13, 2024
e89a37e
update from 24w46a
jacobsjo Nov 13, 2024
52a3252
fix imports
jacobsjo Nov 13, 2024
0350a6e
store id in itemstack
jacobsjo Nov 13, 2024
bbd3983
implement most special models
jacobsjo Nov 14, 2024
171e3e3
improve item registry
jacobsjo Nov 14, 2024
cab2ccf
implement bundle/selected_item
jacobsjo Nov 14, 2024
290af74
implement bundle/fullness
jacobsjo Nov 14, 2024
4cb1fbd
remove local_time, fix chest special renderer
jacobsjo Nov 14, 2024
0bce53b
minor fixes
jacobsjo Nov 14, 2024
c1b281a
add tests
jacobsjo Nov 14, 2024
f6dc6b0
fix defaults of properties and tints
jacobsjo Nov 16, 2024
9649fea
add more tests
jacobsjo Nov 19, 2024
623599e
undo unnecessary formatting changes
jacobsjo Nov 19, 2024
7f0533d
update item_definition url
jacobsjo Nov 19, 2024
eb6229d
add changes from 1.21.4-pre1
jacobsjo Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
input {
padding: 4px;
margin-left: 8px;
width: 500px;
}
.invalid {
color: #cb0000;
Expand Down
39 changes: 33 additions & 6 deletions demo/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { mat4 } from 'gl-matrix'
import type { Resources, Voxel } from '../src/index.js'
import { BlockDefinition, BlockModel, Identifier, ItemRenderer, ItemStack, NormalNoise, Structure, StructureRenderer, TextureAtlas, upperPowerOfTwo, VoxelRenderer, XoroshiroRandom } from '../src/index.js'
import type { ItemRendererResources, ItemRenderingContext, NbtTag, Resources, Voxel } from '../src/index.js'
import { BlockDefinition, BlockModel, Identifier, Item, ItemRenderer, ItemStack, NormalNoise, Structure, StructureRenderer, TextureAtlas, VoxelRenderer, XoroshiroRandom, jsonToNbt, upperPowerOfTwo } from '../src/index.js'
import { } from '../src/nbt/Util.js'
import { ItemModel } from '../src/render/ItemModel.js'


class InteractiveCanvas {
Expand Down Expand Up @@ -66,14 +68,16 @@ Promise.all([
fetch(`${MCMETA}registries/item/data.min.json`).then(r => r.json()),
fetch(`${MCMETA}summary/assets/block_definition/data.min.json`).then(r => r.json()),
fetch(`${MCMETA}summary/assets/model/data.min.json`).then(r => r.json()),
fetch(`${MCMETA}summary/data/item_definition/data.min.json`).then(r => r.json()),
misode marked this conversation as resolved.
Show resolved Hide resolved
fetch(`${MCMETA}summary/item_components/data.min.json`).then(r => r.json()),
fetch(`${MCMETA}atlas/all/data.min.json`).then(r => r.json()),
new Promise<HTMLImageElement>(res => {
const image = new Image()
image.onload = () => res(image)
image.crossOrigin = 'Anonymous'
image.src = `${MCMETA}atlas/all/atlas.png`
}),
]).then(([items, blockstates, models, uvMap, atlas]) => {
]).then(([items, blockstates, models, item_models, item_components, uvMap, atlas]) => {

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

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


const itemModels: Record<string, ItemModel> = {}
Object.keys(item_models).forEach(id => {
itemModels['minecraft:' + id] = ItemModel.fromJson(item_models[id].model)
})


Object.keys(item_components).forEach(id => {
const components = new Map<string, NbtTag>()
Object.keys(item_components[id]).forEach(c_id => {
components.set(c_id, jsonToNbt(item_components[id][c_id]))
})
Item.REGISTRY.register(Identifier.create(id), new Item(components))
})

const atlasCanvas = document.createElement('canvas')
const atlasSize = upperPowerOfTwo(Math.max(atlas.width, atlas.height))
atlasCanvas.width = atlasSize
Expand All @@ -112,28 +131,36 @@ Promise.all([
})
const textureAtlas = new TextureAtlas(atlasData, idMap)

const resources: Resources = {
const resources: Resources & ItemRendererResources = {
getBlockDefinition(id) { return blockDefinitions[id.toString()] },
getBlockModel(id) { return blockModels[id.toString()] },
getTextureUV(id) { return textureAtlas.getTextureUV(id) },
getTextureAtlas() { return textureAtlas.getTextureAtlas() },
getBlockFlags(id) { return { opaque: false } },
getBlockProperties(id) { return null },
getDefaultBlockProperties(id) { return null },
getItemModel(id) { return itemModels[id.toString()] },
}

// === Item rendering ===

const context: ItemRenderingContext = {
"bundle/selected_item": 0
}

const itemCanvas = document.getElementById('item-display') as HTMLCanvasElement
const itemGl = itemCanvas.getContext('webgl')!
const itemInput = document.getElementById('item-input') as HTMLInputElement
itemInput.value = localStorage.getItem('deepslate_demo_item') ?? 'stone'
const itemRenderer = new ItemRenderer(itemGl, Identifier.parse(itemInput.value), resources)
const itemStack = ItemStack.fromString(itemInput.value)
const itemRenderer = new ItemRenderer(itemGl, itemStack, resources, context)

itemInput.addEventListener('keyup', () => {
try {
const id = itemInput.value
itemRenderer.setItem(new ItemStack(Identifier.parse(id), 1))
const itemStack = ItemStack.fromString(itemInput.value)
itemGl.clear(itemGl.DEPTH_BUFFER_BIT | itemGl.COLOR_BUFFER_BIT);
itemRenderer.setItem(itemStack, context)
itemRenderer.drawItem()
itemInput.classList.remove('invalid')
localStorage.setItem('deepslate_demo_item', id)
Expand Down
9 changes: 4 additions & 5 deletions src/core/Effects.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { NbtCompound, NbtTag } from '../nbt/index.js'
import { NbtType } from '../nbt/index.js'
import type { Color } from '../util/index.js'
import { intToRgb } from '../util/index.js'
import { Color } from '../util/index.js'
import { Identifier } from './Identifier.js'

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

export function getColor(contents: PotionContents): Color {
if (contents.customColor) {
return intToRgb(contents.customColor)
return Color.intToRgb(contents.customColor)
}
const effects = getAllEffects(contents)
return mixEffectColors(effects)
Expand All @@ -162,15 +161,15 @@ export namespace PotionContents {
for (const effect of effects) {
const color = EFFECT_COLORS.get(effect.effect.toString())
if (color === undefined) continue
const rgb = intToRgb(color)
const rgb = Color.intToRgb(color)
const amplifier = effect.amplifier + 1
r += amplifier * rgb[0]
g += amplifier * rgb[1]
b += amplifier * rgb[2]
total += amplifier
}
if (total === 0) {
return intToRgb(-13083194)
return Color.intToRgb(-13083194)
}
r = r / total
g = g / total
Expand Down
31 changes: 31 additions & 0 deletions src/core/Item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NbtTag } from "../nbt/index.js"
import { Identifier } from "./index.js"
import { Registry } from "./Registry.js"


export class Item {
public static REGISTRY = Registry.createAndRegister<Item>('item')

constructor(
public components: Map<string, NbtTag> = new Map(),
) {
}

public getComponent<T>(key: string | Identifier, reader: (tag: NbtTag) => T) {
if (typeof key === 'string') {
key = Identifier.parse(key)
}
const value = this.components.get(key.toString())
if (value) {
return reader(value)
}
return undefined
}

public hasComponent(key: string | Identifier) {
if (typeof key === 'string') {
key = Identifier.parse(key)
}
return this.components.has(key.toString())
}
}
77 changes: 70 additions & 7 deletions src/core/ItemStack.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
import { NbtParser } from '../nbt/NbtParser.js'
import type { NbtTag } from '../nbt/index.js'
import { NbtCompound, NbtInt, NbtString } from '../nbt/index.js'
import { StringReader } from '../util/index.js'
import { Holder } from './Holder.js'
import { Identifier } from './Identifier.js'
import { Item } from './Item.js'

export class ItemStack {
private readonly item: Holder<Item | undefined>

constructor(
public id: Identifier,
public readonly id: Identifier,
public count: number,
public components: Map<string, NbtTag> = new Map(),
) {}
public readonly components: Map<string, NbtTag> = new Map(),
) {
this.item = Holder.reference(Item.REGISTRY, id, false)
}

public getComponent<T>(key: string | Identifier, reader: (tag: NbtTag) => T) {
public getComponent<T>(key: string | Identifier, reader: (tag: NbtTag) => T, includeDefaultComponents: boolean = true): T | undefined {
if (typeof key === 'string') {
key = Identifier.parse(key)
}

if (this.components.has('!' + key.toString())){
return undefined
}
const value = this.components.get(key.toString())
if (value) {
return reader(value)
}
return undefined
return includeDefaultComponents ? this.item.value()?.getComponent(key, reader) : undefined
}

public hasComponent(key: string | Identifier) {
public hasComponent(key: string | Identifier, includeDefaultComponents: boolean = true): boolean {
if (typeof key === 'string') {
key = Identifier.parse(key)
}
return this.components.has(key.toString())
if (this.components.has('!' + key.toString())){
return false
}

return this.components.has(key.toString()) || (includeDefaultComponents && (this.item.value()?.hasComponent(key) ?? false))
}

public clone(): ItemStack {
Expand Down Expand Up @@ -77,6 +93,53 @@ export class ItemStack {
return result
}

public static fromString(string: string) {
const reader = new StringReader(string)

while (reader.canRead() && reader.peek() !== '[') {
reader.skip()
}
const itemId = Identifier.parse(reader.getRead())
if (!reader.canRead()){
return new ItemStack(itemId, 1)
}

const components = new Map<string, NbtTag>()
reader.skip()
if (reader.peek() === ']'){
return new ItemStack(itemId, 1, components)
}
do{
if (reader.peek() === '!'){
reader.skip()
const start = reader.cursor
while (reader.canRead() && reader.peek() !== ']' && reader.peek() !== ',') {
reader.skip()
}
components.set('!' + Identifier.parse(reader.getRead(start)).toString(), new NbtCompound())
} else {
const start = reader.cursor
while (reader.canRead() && reader.peek() !== '=') {
reader.skip()
}
const component = Identifier.parse(reader.getRead(start)).toString()
if (!reader.canRead()) break;
reader.skip()
const tag = NbtParser.readTag(reader)
components.set(component, tag)
}
if (!reader.canRead()) break;
if (reader.peek() === ']'){
return new ItemStack(itemId, 1, components)
}
if (reader.peek() !== ','){
throw new Error('Expected , or ]')
}
reader.skip()
} while (reader.canRead())
throw new Error('Missing closing ]')
}

public toNbt() {
const result = new NbtCompound()
.set('id', new NbtString(this.id.toString()))
Expand Down
2 changes: 2 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export * from './Effects.js'
export * from './Holder.js'
export * from './HolderSet.js'
export * from './Identifier.js'
export * from './Item.js'
export * from './ItemStack.js'
export * from './PalettedContainer.js'
export * from './Registry.js'
export * from './Rotation.js'
export * from './Structure.js'
export * from './StructureProvider.js'

23 changes: 23 additions & 0 deletions src/nbt/tags/Util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NbtByte, NbtCompound, NbtDouble, NbtInt, NbtList, NbtString, NbtTag } from "./index.js"

export function jsonToNbt(value: unknown): NbtTag {
if (typeof value === 'string') {
return new NbtString(value)
}
if (typeof value === 'number') {
return Number.isInteger(value) ? new NbtInt(value) : new NbtDouble(value)
}
if (typeof value === 'boolean') {
return new NbtByte(value)
}
if (Array.isArray(value)) {
return new NbtList(value.map(jsonToNbt))
}
if (typeof value === 'object' && value !== null) {
return new NbtCompound(
new Map(Object.entries(value ?? {})
.map(([k, v]) => [k, jsonToNbt(v)]))
)
}
return new NbtByte(0)
}
2 changes: 2 additions & 0 deletions src/nbt/tags/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export * from './NbtShort.js'
export * from './NbtString.js'
export * from './NbtTag.js'
export * from './NbtType.js'
export * from './Util.js'

15 changes: 7 additions & 8 deletions src/render/BlockColors.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { clamp } from '../math/index.js'
import type { Color } from '../util/index.js'
import { intToRgb } from '../util/index.js'
import { Color } from '../util/index.js'

const grass: Color = [124 / 255, 189 / 255, 107 / 255]
const spruce = intToRgb(6396257)
const birch = intToRgb(8431445)
const foliage = intToRgb(4764952)
const water = intToRgb(4159204)
const attached_stem = intToRgb(8431445)
const lily_pad = intToRgb(2129968)
const spruce = Color.intToRgb(6396257)
const birch = Color.intToRgb(8431445)
const foliage = Color.intToRgb(4764952)
const water = Color.intToRgb(4159204)
const attached_stem = Color.intToRgb(8431445)
const lily_pad = Color.intToRgb(2129968)

const redstone = (power: number): Color => {
const a = power / 15
Expand Down
2 changes: 1 addition & 1 deletion src/render/BlockModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { TextureAtlasProvider, UV } from './TextureAtlas.js'

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

type Display = 'thirdperson_righthand' | 'thirdperson_lefthand' | 'firstperson_righthand' | 'firstperson_lefthand' | 'gui' | 'head' | 'ground' | 'fixed'
export type Display = 'thirdperson_righthand' | 'thirdperson_lefthand' | 'firstperson_righthand' | 'firstperson_lefthand' | 'gui' | 'head' | 'ground' | 'fixed' | 'none'

type BlockModelFace = {
texture: string,
Expand Down
Loading