mc-assets 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.MD CHANGED
@@ -19,66 +19,19 @@ npm i mc-assets
19
19
  - **Easy to Use Items Textures** - Includes hand-crafted isometric textures for some blocks.
20
20
  - **Block Entities Models** - Includes community-crafted models for block entities.
21
21
 
22
- This module was originally designed as standalone package for [https://mcraft.fun](mcraft.fun) (repo) project so it is easier to maintain, but now it became a library that I think can cover any use case where you need to work with minecraft assets. This library means block states, models data and texture contents, it doesn't cover minecraft-data use cases.
22
+ This module was originally designed as standalone package for [https://mcraft.fun](mcraft.fun) (repo) project so it is easier to maintain, but now it became a library that I think can cover any use case where you need to work with minecraft assets. This library means block states, models data and texture contents, it doesn't cover minecraft-data use cases. With the best block model parser you can build your own renderers, editors, etc.
23
23
 
24
24
  > Bundled modules & block states are version-accurate starting from 1.13.0 (post-flattening) version.
25
25
  > Tested on Node.js 18 and above.
26
26
 
27
27
 
28
- All blockstates + models + all atlas textures for all versions bundled with rsbuild (uncompressed): 4.73 MB.
28
+ All blockstates + models + all atlas textures for all versions bundled with rsbuild (uncompressed): 4.79 MB.
29
29
 
30
30
  This packages includes versions for: 1.7.10, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.8.5, 1.8.6, 1.8.7, 1.8.8, 1.8.9, 1.9, 1.9.1, 1.9.2, 1.9.3, 1.9.4, 1.10, 1.10.1, 1.10.2, 1.11, 1.11.1, 1.11.2, 1.12, 1.12.1, 1.12.2, 1.13, 1.13.1, 1.13.2, 1.14, 1.14.1, 1.14.2, 1.14.3, 1.14.4, 1.15, 1.15.1, 1.15.2, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5, 1.17, 1.17.1, 1.18, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21.
31
31
 
32
32
  <details>
33
33
  <summary>Included Block Entities (Additional Block Models) (130):</summary>
34
34
 
35
- - ✅ sign
36
- - ✅ wall_sign
37
- - ✅ acacia_sign
38
- - ✅ acacia_wall_sign
39
- - ✅ birch_sign
40
- - ✅ birch_wall_sign
41
- - ✅ dark_oak_sign
42
- - ✅ dark_oak_wall_sign
43
- - ✅ jungle_sign
44
- - ✅ jungle_wall_sign
45
- - ✅ oak_sign
46
- - ✅ oak_wall_sign
47
- - ✅ spruce_sign
48
- - ✅ spruce_wall_sign
49
- - ✅ crimson_sign
50
- - ✅ crimson_wall_sign
51
- - ✅ warped_sign
52
- - ✅ warped_wall_sign
53
- - ✅ mangrove_sign
54
- - ✅ mangrove_wall_sign
55
- - ✅ decorated_pot
56
- - ✅ acacia_hanging_sign
57
- - ✅ acacia_wall_hanging_sign
58
- - ✅ bamboo_hanging_sign
59
- - ✅ bamboo_sign
60
- - ✅ bamboo_wall_hanging_sign
61
- - ✅ bamboo_wall_sign
62
- - ✅ birch_hanging_sign
63
- - ✅ birch_wall_hanging_sign
64
- - ✅ cherry_hanging_sign
65
- - ✅ cherry_sign
66
- - ✅ cherry_wall_hanging_sign
67
- - ✅ cherry_wall_sign
68
- - ✅ crimson_hanging_sign
69
- - ✅ crimson_wall_hanging_sign
70
- - ✅ dark_oak_hanging_sign
71
- - ✅ dark_oak_wall_hanging_sign
72
- - ✅ jungle_hanging_sign
73
- - ✅ jungle_wall_hanging_sign
74
- - ✅ mangrove_hanging_sign
75
- - ✅ mangrove_wall_hanging_sign
76
- - ✅ oak_hanging_sign
77
- - ✅ oak_wall_hanging_sign
78
- - ✅ spruce_hanging_sign
79
- - ✅ spruce_wall_hanging_sign
80
- - ✅ warped_hanging_sign
81
- - ✅ warped_wall_hanging_sign
82
35
  - ✅ black_banner
83
36
  - ✅ black_bed
84
37
  - ✅ black_shulker_box
@@ -130,8 +83,6 @@ This packages includes versions for: 1.7.10, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.
130
83
  - ✅ orange_bed
131
84
  - ✅ orange_shulker_box
132
85
  - ✅ orange_wall_banner
133
- - ✅ piglin_head
134
- - ✅ piglin_wall_head
135
86
  - ✅ pink_banner
136
87
  - ✅ pink_bed
137
88
  - ✅ pink_shulker_box
@@ -147,9 +98,11 @@ This packages includes versions for: 1.7.10, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.
147
98
  - ✅ red_shulker_box
148
99
  - ✅ red_wall_banner
149
100
  - ✅ shulker_box
101
+ - ✅ sign
150
102
  - ✅ skeleton_skull
151
103
  - ✅ skeleton_wall_skull
152
104
  - ✅ trapped_chest
105
+ - ✅ wall_sign
153
106
  - ✅ white_banner
154
107
  - ✅ white_bed
155
108
  - ✅ white_shulker_box
@@ -162,6 +115,53 @@ This packages includes versions for: 1.7.10, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.
162
115
  - ✅ yellow_wall_banner
163
116
  - ✅ zombie_head
164
117
  - ✅ zombie_wall_head
118
+ - ✅ acacia_sign
119
+ - ✅ acacia_wall_sign
120
+ - ✅ birch_sign
121
+ - ✅ birch_wall_sign
122
+ - ✅ dark_oak_sign
123
+ - ✅ dark_oak_wall_sign
124
+ - ✅ jungle_sign
125
+ - ✅ jungle_wall_sign
126
+ - ✅ oak_sign
127
+ - ✅ oak_wall_sign
128
+ - ✅ spruce_sign
129
+ - ✅ spruce_wall_sign
130
+ - ✅ crimson_sign
131
+ - ✅ crimson_wall_sign
132
+ - ✅ warped_sign
133
+ - ✅ warped_wall_sign
134
+ - ✅ mangrove_sign
135
+ - ✅ mangrove_wall_sign
136
+ - ✅ acacia_hanging_sign
137
+ - ✅ acacia_wall_hanging_sign
138
+ - ✅ bamboo_hanging_sign
139
+ - ✅ bamboo_sign
140
+ - ✅ bamboo_wall_hanging_sign
141
+ - ✅ bamboo_wall_sign
142
+ - ✅ birch_hanging_sign
143
+ - ✅ birch_wall_hanging_sign
144
+ - ✅ crimson_hanging_sign
145
+ - ✅ crimson_wall_hanging_sign
146
+ - ✅ dark_oak_hanging_sign
147
+ - ✅ dark_oak_wall_hanging_sign
148
+ - ✅ jungle_hanging_sign
149
+ - ✅ jungle_wall_hanging_sign
150
+ - ✅ mangrove_hanging_sign
151
+ - ✅ mangrove_wall_hanging_sign
152
+ - ✅ oak_hanging_sign
153
+ - ✅ oak_wall_hanging_sign
154
+ - ✅ piglin_head
155
+ - ✅ piglin_wall_head
156
+ - ✅ spruce_hanging_sign
157
+ - ✅ spruce_wall_hanging_sign
158
+ - ✅ warped_hanging_sign
159
+ - ✅ warped_wall_hanging_sign
160
+ - ✅ cherry_hanging_sign
161
+ - ✅ cherry_sign
162
+ - ✅ cherry_wall_hanging_sign
163
+ - ✅ cherry_wall_sign
164
+ - ✅ decorated_pot
165
165
  - ❌ black_candle
166
166
  - ❌ blue_candle
167
167
  - ❌ brown_candle
@@ -195,6 +195,8 @@ This packages includes versions for: 1.7.10, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.
195
195
 
196
196
  [Atlas explorer (web demo)](https://zardoy.github.io/mc-assets/).
197
197
 
198
+ [External Models Playground](https://mcraft.fun/playground.html)
199
+
198
200
  ## Usage
199
201
 
200
202
  Following atlases are generated:
@@ -223,38 +225,50 @@ const model = modelsStore.get('latest', 'block/stone_mirrored')
223
225
  // note: you can always specify a specific version instead of 'latest'
224
226
  const assetsParser = new AssetsParser('latest', blockstatesStore, modelsStore)
225
227
 
226
- const resolvedModel = assetsParser.getResolvedModel({
228
+ const resolvedModel = assetsParser.getAllResolvedModels({
227
229
  name: 'stone', // block name not model name
228
230
  properties: {},
229
231
  }, false) // false (default) means return empty if variant matching properties is not found
230
- // {
231
- // textures: {
232
- // all: 'block/stone',
233
- // particle: 'block/stone',
234
- // down: 'block/stone',
235
- // up: 'block/stone',
236
- // north: 'block/stone',
237
- // east: 'block/stone',
238
- // south: 'block/stone',
239
- // west: 'block/stone'
240
- // },
241
- // elements: [
232
+ // [
233
+ // [
242
234
  // {
243
- // from: [ 0, 0, 0 ],
244
- // to: [ 16, 16, 16 ],
245
- // faces: {
246
- // down: { texture: 'block/stone', cullface: 'down' },
247
- // up: { texture: 'block/stone', cullface: 'up' },
248
- // north: { texture: 'block/stone', cullface: 'north' },
249
- // south: { texture: 'block/stone', cullface: 'south' },
250
- // west: { texture: 'block/stone', cullface: 'west' },
251
- // east: { texture: 'block/stone', cullface: 'east' }
252
- // }
235
+ // x: undefined,
236
+ // y: undefined,
237
+ // z: undefined,
238
+ // uvlock: undefined,
239
+ // weight: undefined,
240
+ // textures: {
241
+ // all: 'block/stone',
242
+ // particle: 'block/stone',
243
+ // down: 'block/stone',
244
+ // up: 'block/stone',
245
+ // north: 'block/stone',
246
+ // east: 'block/stone',
247
+ // south: 'block/stone',
248
+ // west: 'block/stone'
249
+ // },
250
+ // elements: [
251
+ // {
252
+ // from: [ 0, 0, 0 ],
253
+ // to: [ 16, 16, 16 ],
254
+ // faces: {
255
+ // down: { texture: 'block/stone', cullface: 'down' },
256
+ // up: { texture: 'block/stone', cullface: 'up' },
257
+ // north: { texture: 'block/stone', cullface: 'north' },
258
+ // south: { texture: 'block/stone', cullface: 'south' },
259
+ // west: { texture: 'block/stone', cullface: 'west' },
260
+ // east: { texture: 'block/stone', cullface: 'east' }
261
+ // }
262
+ // }
263
+ // ]
253
264
  // }
254
- // ]
255
- // }
265
+ // ],
266
+ // [ ... ] // second variant and so on
267
+ // ]
256
268
  ```
257
269
 
270
+ `getAllResolvedModels` returns an array of arrays, where first depth level is model parts, second depth level is variants (for example Minecraft chooses them randomly). You can use `getResolvedModelFirst` to get first variant only. Or `getResolvedModelRandom` to get a random variant.
271
+
258
272
  ### Rendering Atlas Textures
259
273
 
260
274
  ```ts
@@ -14,39 +14,31 @@ export declare class AssetsParser {
14
14
  blockModelsStore: BlockModelsStore;
15
15
  stateIdToElements: Record<string, BlockElement[] | 1>;
16
16
  constructor(version: string, blockStatesStore: BlockStatesStore, blockModelsStore: BlockModelsStore);
17
- getElementsCached(queriedBlock: QueriedBlock): number | BlockElement[];
17
+ getElementsCached(queriedBlock: QueriedBlock): 1 | BlockElement[];
18
+ parseProperties(properties: string): Record<string, string | boolean>;
19
+ getElements(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): 0 | 1 | BlockElement[];
18
20
  private resolvedModel;
19
- getElements(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): number | BlockElement[];
20
- getResolvedModelByModelName(model: string, debugQueryName?: string, clearModel?: boolean): {
21
- elementsOptimized: number | BlockElement[];
22
- resolvedModel: Pick<BlockModel, "x" | "y" | "z" | "textures" | "ao" | "elements">;
23
- } | undefined;
24
- getResolvedModel(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): {
21
+ private getModelsByBlock;
22
+ private getResolvedModelsByModel;
23
+ getResolvedModelFirst(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): (Pick<BlockModel, "textures" | "ao" | "elements"> & {
25
24
  x?: number;
26
25
  y?: number;
27
26
  z?: number;
28
- textures?: {
29
- [name: string]: string;
30
- };
31
- ao?: boolean;
32
- elements?: {
33
- from: BlockElementPos;
34
- to: BlockElementPos;
35
- rotation?: {
36
- origin: [number, number, number];
37
- axis: string;
38
- angle: number;
39
- rescale?: boolean;
40
- };
41
- faces: {
42
- [name: string]: {
43
- texture: string;
44
- uv?: number[];
45
- cullface?: string;
46
- rotation?: number;
47
- tintindex?: number;
48
- };
49
- };
50
- }[];
51
- };
27
+ uvlock?: boolean;
28
+ weight?: number;
29
+ })[] | undefined;
30
+ getResolvedModelRandom(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): (Pick<BlockModel, "textures" | "ao" | "elements"> & {
31
+ x?: number;
32
+ y?: number;
33
+ z?: number;
34
+ uvlock?: boolean;
35
+ weight?: number;
36
+ })[] | undefined;
37
+ getAllResolvedModels(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): (Pick<BlockModel, "textures" | "ao" | "elements"> & {
38
+ x?: number;
39
+ y?: number;
40
+ z?: number;
41
+ uvlock?: boolean;
42
+ weight?: number;
43
+ })[][] | undefined;
52
44
  }
@@ -17,26 +17,39 @@ export class AssetsParser {
17
17
  this.stateIdToElements[stateId] = final;
18
18
  return final;
19
19
  }
20
- // looks like workaround
21
- resolvedModel = {};
20
+ parseProperties(properties) {
21
+ if (typeof properties === 'object') {
22
+ return properties;
23
+ }
24
+ const json = {};
25
+ for (const prop of properties.split(',')) {
26
+ const [key, value] = prop.split('=');
27
+ json[key] = value;
28
+ }
29
+ return json;
30
+ }
22
31
  getElements(queriedBlock, fallbackVariant = false) {
23
- function parseProperties(properties) {
24
- if (typeof properties === 'object') {
25
- return properties;
26
- }
27
- const json = {};
28
- for (const prop of properties.split(',')) {
29
- const [key, value] = prop.split('=');
30
- json[key] = value;
31
- }
32
- return json;
32
+ const model = this.getResolvedModelFirst(queriedBlock, fallbackVariant);
33
+ if (!model)
34
+ return 0;
35
+ const allElements = [];
36
+ for (const m of model) {
37
+ if (!m?.elements)
38
+ continue;
39
+ allElements.push(...m.elements.map(({ from, to }) => [from, to]));
33
40
  }
34
- function matchProperties(block, /* to match against */ properties) {
41
+ const elementsOptimized = allElements.length === 1 && arrEq(allElements[0][0], [0, 0, 0]) && arrEq(allElements[0][1], [16, 16, 16]) ? 1 : allElements;
42
+ return elementsOptimized;
43
+ }
44
+ // looks like workaround
45
+ resolvedModel = {};
46
+ getModelsByBlock(queriedBlock, fallbackVariant, multiOptim) {
47
+ const matchProperties = (block, /* to match against */ properties) => {
35
48
  if (!properties) {
36
49
  return true;
37
50
  }
38
51
  if (typeof properties === 'string') {
39
- properties = parseProperties(properties);
52
+ properties = this.parseProperties(properties);
40
53
  }
41
54
  const blockProps = block.properties;
42
55
  if (properties.OR) {
@@ -50,11 +63,11 @@ export class AssetsParser {
50
63
  }
51
64
  }
52
65
  return true;
53
- }
54
- let modelApply;
66
+ };
67
+ let applyModels = [];
55
68
  const blockStates = this.blockStatesStore.get(this.version, queriedBlock.name);
56
69
  if (!blockStates)
57
- return 0;
70
+ return;
58
71
  const states = blockStates.variants;
59
72
  if (states) {
60
73
  let state = states[''] || states['normal'];
@@ -71,42 +84,62 @@ export class AssetsParser {
71
84
  state = states[Object.keys(states)[0]];
72
85
  }
73
86
  else {
74
- return 0;
87
+ return;
75
88
  }
76
89
  }
77
- modelApply = state;
90
+ if (state) {
91
+ applyModels.push(state);
92
+ }
78
93
  }
79
94
  if (blockStates.multipart) {
80
- // TODO! multiple variants
81
- const multipartWithWhen = blockStates.multipart.filter(x => x.when);
82
- const multipartWithoutWhen = blockStates.multipart.filter(x => !x.when);
83
- for (const { when, apply } of multipartWithWhen) {
84
- if (matchProperties(queriedBlock, when)) {
85
- modelApply = apply;
95
+ for (const { when, apply } of blockStates.multipart) {
96
+ if (!when || matchProperties(queriedBlock, when)) {
97
+ applyModels.push(apply);
86
98
  }
87
99
  }
88
- if (!modelApply) {
89
- modelApply = multipartWithoutWhen[0]?.apply;
100
+ if (!applyModels.length && fallbackVariant) {
101
+ const multipartWithWhen = blockStates.multipart.filter(x => x.when);
102
+ const apply = multipartWithWhen[0]?.apply;
103
+ if (apply) {
104
+ applyModels.push(apply);
105
+ }
90
106
  }
91
- if (!modelApply && fallbackVariant) {
92
- modelApply = multipartWithWhen[0]?.apply;
107
+ }
108
+ if (!applyModels.length)
109
+ return;
110
+ const modelsResolved = [];
111
+ let part = 0;
112
+ for (const model of applyModels) {
113
+ part++;
114
+ let variant = 0;
115
+ modelsResolved.push([]);
116
+ for (const varModel of Array.isArray(model) ? model : [model]) {
117
+ variant++;
118
+ this.resolvedModel = {
119
+ x: varModel.x,
120
+ y: varModel.y,
121
+ z: varModel.z,
122
+ uvlock: varModel.uvlock,
123
+ weight: varModel.weight,
124
+ };
125
+ if (!varModel)
126
+ continue;
127
+ this.getResolvedModelsByModel(varModel.model, queriedBlock.name + '-' + part + '-' + variant, false);
128
+ // if (!result || Object.keys(result).length === 0) continue // todo: maybe we should push null?
129
+ modelsResolved[modelsResolved.length - 1].push(this.resolvedModel);
130
+ if (!multiOptim && modelsResolved[modelsResolved.length - 1].length > 0)
131
+ break;
93
132
  }
94
133
  }
95
- if (!modelApply)
96
- return 0;
97
- // TODO losing x, y, uvlock, and not 0!
98
- const model = (Array.isArray(modelApply) ? modelApply[0 /* Math.floor(Math.random() * modelApply.length) */] : modelApply).model;
99
- return this.getResolvedModelByModelName(model, queriedBlock.name)?.elementsOptimized ?? 0;
134
+ return modelsResolved; // todo figure out the type error
100
135
  }
101
- getResolvedModelByModelName(model, debugQueryName, clearModel = true) {
136
+ getResolvedModelsByModel(model, debugQueryName, clearModel = true) {
102
137
  if (clearModel) {
103
138
  this.resolvedModel = {};
104
139
  }
105
140
  const modelData = this.blockModelsStore.get(this.version, model);
106
141
  if (!modelData)
107
142
  return;
108
- // let textures = {} as Record<string, string>
109
- let elements = [];
110
143
  const resolveModel = (model) => {
111
144
  if (model.ambientocclusion !== undefined) {
112
145
  this.resolvedModel.ao = model.ambientocclusion;
@@ -114,21 +147,11 @@ export class AssetsParser {
114
147
  if (model.ao !== undefined) {
115
148
  this.resolvedModel.ao = model.ao;
116
149
  }
117
- if (model.x !== undefined) {
118
- this.resolvedModel.x = model.x;
119
- }
120
- if (model.y !== undefined) {
121
- this.resolvedModel.y = model.y;
122
- }
123
- if (model.z !== undefined) {
124
- this.resolvedModel.z = model.z;
125
- }
126
150
  if (model.textures) {
127
151
  this.resolvedModel.textures ??= {};
128
152
  Object.assign(this.resolvedModel.textures, model.textures);
129
153
  }
130
154
  if (model.elements) {
131
- elements.push(...model.elements.map(({ from, to }) => [from, to]));
132
155
  this.resolvedModel.elements ??= [];
133
156
  this.resolvedModel.elements.push(...structuredClone(model.elements));
134
157
  }
@@ -174,21 +197,18 @@ export class AssetsParser {
174
197
  face.texture = this.resolvedModel.textures[face.texture.replace('#', '')] ?? face.texture;
175
198
  }
176
199
  }
177
- // todo cleanup methods
178
200
  return {
179
- elementsOptimized: elements.length === 1 && arrEq(elements[0][0], [0, 0, 0]) && arrEq(elements[0][1], [16, 16, 16]) ? 1 : elements,
180
201
  resolvedModel: this.resolvedModel,
181
202
  };
182
203
  }
183
- getResolvedModel(queriedBlock, fallbackVariant = false) {
184
- this.resolvedModel = {};
185
- const elements = this.getElements(queriedBlock, fallbackVariant);
186
- return {
187
- ...this.resolvedModel,
188
- // elements: elements === 0 ? [] : elements === 1 ? [
189
- // [[0, 0, 0], [16, 16, 16]]
190
- // ] : elements,
191
- };
204
+ getResolvedModelFirst(queriedBlock, fallbackVariant = false) {
205
+ return this.getModelsByBlock(queriedBlock, fallbackVariant, false)?.map(x => x[0]);
206
+ }
207
+ getResolvedModelRandom(queriedBlock, fallbackVariant = false) {
208
+ return this.getModelsByBlock(queriedBlock, fallbackVariant, false)?.map(x => x[Math.floor(Math.random() * x.length)]);
209
+ }
210
+ getAllResolvedModels(queriedBlock, fallbackVariant = false) {
211
+ return this.getModelsByBlock(queriedBlock, fallbackVariant, true);
192
212
  }
193
213
  }
194
214
  const arrEq = (a, b) => !!a && !!b && a.length === b.length && a.every((v, i) => v === b[i]);