Skip to content

Commit 8a7501f

Browse files
committed
✨ Preview item display transform in Blockbench
1 parent 01e149e commit 8a7501f

File tree

6 files changed

+116
-37
lines changed

6 files changed

+116
-37
lines changed

src/components/vanillaItemDisplayElementPanel.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
let item = selected.item
1212
let error = selected.error
1313
14+
ITEM_DISPLAY_ITEM_DISPLAY_SELECT.set(selected.itemDisplay)
15+
1416
$: {
1517
$error = ''
1618
if (selected.item !== item) {

src/interface/panel/vanillaItemDisplayElement.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,11 @@ ITEM_DISPLAY_ITEM_DISPLAY_SELECT.set = function (
100100
if (VanillaItemDisplay.selected.length > 1) {
101101
for (const display of VanillaItemDisplay.selected) {
102102
display.itemDisplay = value
103+
void display.updateItem()
103104
}
104105
} else {
105106
selected.itemDisplay = value
107+
void selected.updateItem()
106108
}
107109
Project!.saved = false
108110
Undo.finishEdit(`Change Item Display Node's Item Display Mode to ${value}`, {

src/outliner/vanillaItemDisplay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaItemDisplay,
202202
updateGeometry(el: VanillaItemDisplay) {
203203
if (!el.mesh) return
204204

205-
void getItemModel(el.item)
205+
void getItemModel(el.item, el.itemDisplay)
206206
.then(result => {
207207
if (!result) return
208208
const mesh = el.mesh as THREE.Mesh

src/systems/minecraft/blockModelManager.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ItemDisplayMode } from 'src/outliner/vanillaItemDisplay'
12
import { mergeGeometries } from '../../util/bufferGeometryUtils'
23
import {
34
type IParsedBlock,
@@ -8,6 +9,7 @@ import {
89
import { translate } from '../../util/translation'
910
import { assetsLoaded, getJSONAsset, getPngAssetAsDataUrl, hasAsset } from './assetManager'
1011
import type { BlockStateValue } from './blockstateManager'
12+
import { applyModelDisplayTransform } from './itemModelManager'
1113
import type {
1214
IBlockModel,
1315
IBlockState,
@@ -77,7 +79,8 @@ export async function getBlockModel(block: string): Promise<BlockModelMesh | und
7779

7880
export async function parseBlockModel(
7981
variant: IBlockStateVariant,
80-
childModel?: IBlockModel
82+
childModel?: IBlockModel,
83+
itemDisplay: ItemDisplayMode = 'none'
8184
): Promise<BlockModelMesh> {
8285
const modelPath = getPathFromResourceLocation(variant.model, 'models')
8386
const model = getJSONAsset(modelPath + '.json') as IBlockModel
@@ -97,10 +100,12 @@ export async function parseBlockModel(
97100

98101
if (model.parent) {
99102
const parentVariant = { ...variant, model: model.parent }
100-
return await parseBlockModel(parentVariant, model)
103+
return await parseBlockModel(parentVariant, model, itemDisplay)
101104
}
102105

103-
return await generateModelMesh(variant, model)
106+
const mesh = await generateModelMesh(variant, model)
107+
applyModelDisplayTransform(mesh, model, itemDisplay)
108+
return mesh
104109
}
105110

106111
async function generateModelMesh(

src/systems/minecraft/itemModelManager.ts

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
1+
import { ItemDisplayMode } from 'src/outliner/vanillaItemDisplay'
12
import { mergeGeometries } from '../../util/bufferGeometryUtils'
23
import { getPathFromResourceLocation, parseResourceLocation } from '../../util/minecraftUtil'
34
import { assetsLoaded, getJSONAsset, getPngAssetAsDataUrl } from './assetManager'
45
import { parseBlockModel } from './blockModelManager'
56
import type { IItemModel } from './model'
67
import { TEXTURE_FRAG_SHADER, TEXTURE_VERT_SHADER } from './textureShaders'
78

8-
interface ItemModelMesh {
9+
interface ItemMesh {
910
mesh: THREE.Mesh
1011
outline: THREE.LineSegments
1112
boundingBox: THREE.BufferGeometry
1213
isBlock?: boolean
1314
}
1415

1516
const LOADER = new THREE.TextureLoader()
16-
const ITEM_MODEL_CACHE = new Map<string, ItemModelMesh>()
17+
const ITEM_MODEL_CACHE = new Map<string, ItemMesh>()
1718

18-
export async function getItemModel(item: string): Promise<ItemModelMesh | undefined> {
19+
export async function getItemModel(
20+
item: string,
21+
itemDisplay: ItemDisplayMode
22+
): Promise<ItemMesh | undefined> {
1923
await assetsLoaded()
20-
let result = ITEM_MODEL_CACHE.get(item)
24+
const cacheKey = item + '|' + itemDisplay
25+
let result = ITEM_MODEL_CACHE.get(cacheKey)
2126
if (!result) {
22-
// console.warn(`Found no cached item model mesh for '${item}'`)
23-
result = await parseItemModel(getItemResourceLocation(item))
24-
ITEM_MODEL_CACHE.set(item, result)
27+
result = await parseItemModel(getItemResourceLocation(item), itemDisplay)
28+
ITEM_MODEL_CACHE.set(cacheKey, result)
2529
}
2630
if (!result) return undefined
2731
result = {
@@ -48,7 +52,64 @@ function getItemResourceLocation(item: string) {
4852
return resource.namespace + ':' + 'item/' + resource.path
4953
}
5054

51-
async function parseItemModel(location: string, childModel?: IItemModel): Promise<ItemModelMesh> {
55+
const GENERATED_ITEM_DISPLAY_SETTINGS: NonNullable<IItemModel['display']> = {
56+
thirdperson_righthand: { translation: [0, 3, 1], scale: [0.55, 0.55, 0.55] },
57+
thirdperson_lefthand: { translation: [0, 3, 1], scale: [0.55, 0.55, 0.55] },
58+
firstperson_righthand: {
59+
rotation: [0, -90, 25],
60+
translation: [1.13, 3.2, 1.13],
61+
scale: [0.68, 0.68, 0.68],
62+
},
63+
firstperson_lefthand: {
64+
rotation: [0, -90, 25],
65+
translation: [1.13, 3.2, 1.13],
66+
scale: [0.68, 0.68, 0.68],
67+
},
68+
ground: { translation: [0, 2, 0], scale: [0.5, 0.5, 0.5] },
69+
head: { rotation: [0, -180, 0], translation: [0, 13, 7] },
70+
fixed: { rotation: [0, -180, 0] },
71+
}
72+
73+
export function applyModelDisplayTransform(
74+
itemModel: ItemMesh,
75+
model: IItemModel,
76+
itemDisplay: ItemDisplayMode
77+
) {
78+
if (itemDisplay === 'none') return
79+
80+
// default to right hand if left hand display is not defined
81+
if (itemDisplay === 'thirdperson_lefthand' && !model.display?.thirdperson_lefthand) {
82+
itemDisplay = 'thirdperson_righthand'
83+
}
84+
if (itemDisplay === 'firstperson_lefthand' && !model.display?.firstperson_lefthand) {
85+
itemDisplay = 'firstperson_righthand'
86+
}
87+
88+
const display = model.display?.[itemDisplay]
89+
if (!display) return
90+
91+
const matrix = new THREE.Matrix4()
92+
if (display.rotation) {
93+
const rot = display.rotation.map((n: number) => (n * Math.PI) / 180)
94+
matrix.makeRotationFromEuler(new THREE.Euler(rot[0], rot[1], rot[2]))
95+
}
96+
if (display.translation) {
97+
matrix.setPosition(new THREE.Vector3(...display.translation))
98+
}
99+
if (display.scale) {
100+
matrix.scale(new THREE.Vector3(...display.scale))
101+
}
102+
103+
itemModel.boundingBox.applyMatrix4(matrix)
104+
itemModel.outline.geometry.applyMatrix4(matrix)
105+
itemModel.mesh.applyMatrix4(matrix)
106+
}
107+
108+
async function parseItemModel(
109+
location: string,
110+
itemDisplay: ItemDisplayMode,
111+
childModel?: IItemModel
112+
): Promise<ItemMesh> {
52113
const modelPath = getPathFromResourceLocation(location, 'models')
53114
let model: IItemModel
54115
try {
@@ -75,22 +136,27 @@ async function parseItemModel(location: string, childModel?: IItemModel): Promis
75136

76137
if (model.parent) {
77138
const resource = parseResourceLocation(model.parent)
78-
console.log('Parsed resource:', resource)
79139
if (resource.type === 'block') {
80-
return await parseBlockModel({ model: model.parent, isItemModel: true }, model)
81-
}
82-
if (resource.path === 'item/generated') {
83-
return await generateItemMesh(location, model)
140+
return await parseBlockModel(
141+
{ model: model.parent, isItemModel: true },
142+
model,
143+
itemDisplay
144+
)
145+
} else if (resource.path === 'item/generated') {
146+
const itemMesh = await generateItemMesh(location, model)
147+
model.display ??= GENERATED_ITEM_DISPLAY_SETTINGS
148+
applyModelDisplayTransform(itemMesh, model, itemDisplay)
149+
return itemMesh
84150
} else {
85-
return await parseItemModel(model.parent, model)
151+
return await parseItemModel(model.parent, itemDisplay, model)
86152
}
87153
} else {
88154
// The block model parser handles custom item models made from elements just fine, so we can use it here
89-
return await parseBlockModel({ model: location, isItemModel: true }, model)
155+
return await parseBlockModel({ model: location, isItemModel: true }, model, itemDisplay)
90156
}
91157
}
92158

93-
async function generateItemMesh(location: string, model: IItemModel): Promise<ItemModelMesh> {
159+
async function generateItemMesh(location: string, model: IItemModel): Promise<ItemMesh> {
94160
const masterMesh = new THREE.Mesh()
95161
const boundingBoxes: THREE.BufferGeometry[] = []
96162
const outlineGeos: THREE.BufferGeometry[] = []

src/systems/minecraft/model.d.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ export interface IItemModelElementFace {
4141
}
4242

4343
export interface IModelELement {
44-
from: [number, number, number]
45-
to: [number, number, number]
44+
from: ArrayVector3
45+
to: ArrayVector3
4646
rotation?: {
47-
origin: [number, number, number]
47+
origin: ArrayVector3
4848
axis: 'x' | 'y' | 'z'
4949
angle: number
5050
rescale: boolean
@@ -56,13 +56,15 @@ export interface IModelELement {
5656
export interface IItemModel {
5757
parent?: string
5858
textures: Record<string, string> & Record<`layer${number}`, string> & { particle?: string }
59-
display?: Record<
60-
ModelDisplaySlot,
61-
{
62-
rotation: [number, number, number]
63-
translation: [number, number, number]
64-
scale: [number, number, number]
65-
}
59+
display?: Partial<
60+
Record<
61+
ModelDisplaySlot,
62+
{
63+
rotation?: ArrayVector3
64+
translation?: ArrayVector3
65+
scale?: ArrayVector3
66+
}
67+
>
6668
>
6769
gui_light?: 'front' | 'side'
6870
elements?: IModelELement[]
@@ -75,13 +77,15 @@ export interface IItemModel {
7577
export interface IBlockModel {
7678
parent?: string
7779
textures: Record<string, string> & Record<`layer${number}`, string> & { particle?: string }
78-
display?: Record<
79-
ModelDisplaySlot,
80-
{
81-
rotation: [number, number, number]
82-
translation: [number, number, number]
83-
scale: [number, number, number]
84-
}
80+
display?: Partial<
81+
Record<
82+
ModelDisplaySlot,
83+
{
84+
rotation?: ArrayVector3
85+
translation?: ArrayVector3
86+
scale?: ArrayVector3
87+
}
88+
>
8589
>
8690
ambientocclusion?: boolean
8791
elements?: IModelELement[]

0 commit comments

Comments
 (0)