mc-assets 0.1.7 → 0.2.1

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,7 +19,7 @@ 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.
@@ -162,32 +162,26 @@ This packages includes versions for: 1.7.10, 1.8, 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.
162
162
  - ✅ yellow_wall_banner
163
163
  - ✅ zombie_head
164
164
  - ✅ zombie_wall_head
165
- - ❌ black_candle
166
- - ❌ blue_candle
167
- - ❌ brown_candle
168
- - ❌ candle
169
- - ❌ cyan_candle
165
+ - ❌ black_glazed_terracotta
166
+ - ❌ blue_glazed_terracotta
167
+ - ❌ brown_glazed_terracotta
168
+ - ❌ cyan_glazed_terracotta
170
169
  - ❌ end_gateway
171
170
  - ❌ end_portal
172
- - ❌ gray_candle
173
- - ❌ green_candle
174
- - ❌ light_blue_candle
175
- - ❌ light_gray_candle
176
- - ❌ lime_candle
177
- - ❌ magenta_candle
178
- - ❌ orange_candle
179
- - ❌ pink_candle
180
- - ❌ pink_petals
181
- - ❌ powder_snow_cauldron
182
- - ❌ purple_candle
183
- - ❌ red_candle
184
- - ❌ repeater
185
- - ❌ sea_pickle
171
+ - ❌ gray_glazed_terracotta
172
+ - ❌ green_glazed_terracotta
173
+ - ❌ light_blue_glazed_terracotta
174
+ - ❌ light_gray_glazed_terracotta
175
+ - ❌ lime_glazed_terracotta
176
+ - ❌ magenta_glazed_terracotta
177
+ - ❌ orange_glazed_terracotta
178
+ - ❌ pink_glazed_terracotta
179
+ - ❌ purple_glazed_terracotta
180
+ - ❌ red_glazed_terracotta
186
181
  - ❌ structure_void
187
- - ❌ turtle_egg
188
- - ❌ water_cauldron
189
- - ❌ white_candle
190
- - ❌ yellow_candle
182
+ - ❌ trial_spawner
183
+ - ❌ white_glazed_terracotta
184
+ - ❌ yellow_glazed_terracotta
191
185
 
192
186
  </details>
193
187
 
@@ -195,6 +189,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
189
 
196
190
  [Atlas explorer (web demo)](https://zardoy.github.io/mc-assets/).
197
191
 
192
+ [External Models Playground](https://mcraft.fun/playground.html)
193
+
198
194
  ## Usage
199
195
 
200
196
  Following atlases are generated:
@@ -223,38 +219,50 @@ const model = modelsStore.get('latest', 'block/stone_mirrored')
223
219
  // note: you can always specify a specific version instead of 'latest'
224
220
  const assetsParser = new AssetsParser('latest', blockstatesStore, modelsStore)
225
221
 
226
- const resolvedModel = assetsParser.getResolvedModel({
222
+ const resolvedModel = assetsParser.getAllResolvedModels({
227
223
  name: 'stone', // block name not model name
228
224
  properties: {},
229
225
  }, 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: [
226
+ // [
227
+ // [
242
228
  // {
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
- // }
229
+ // x: undefined,
230
+ // y: undefined,
231
+ // z: undefined,
232
+ // uvlock: undefined,
233
+ // weight: undefined,
234
+ // textures: {
235
+ // all: 'block/stone',
236
+ // particle: 'block/stone',
237
+ // down: 'block/stone',
238
+ // up: 'block/stone',
239
+ // north: 'block/stone',
240
+ // east: 'block/stone',
241
+ // south: 'block/stone',
242
+ // west: 'block/stone'
243
+ // },
244
+ // elements: [
245
+ // {
246
+ // from: [ 0, 0, 0 ],
247
+ // to: [ 16, 16, 16 ],
248
+ // faces: {
249
+ // down: { texture: 'block/stone', cullface: 'down' },
250
+ // up: { texture: 'block/stone', cullface: 'up' },
251
+ // north: { texture: 'block/stone', cullface: 'north' },
252
+ // south: { texture: 'block/stone', cullface: 'south' },
253
+ // west: { texture: 'block/stone', cullface: 'west' },
254
+ // east: { texture: 'block/stone', cullface: 'east' }
255
+ // }
256
+ // }
257
+ // ]
253
258
  // }
254
- // ]
255
- // }
259
+ // ],
260
+ // [ ... ] // second variant and so on
261
+ // ]
256
262
  ```
257
263
 
264
+ `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.
265
+
258
266
  ### Rendering Atlas Textures
259
267
 
260
268
  ```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]);