mc-assets 0.1.0 → 0.1.2

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
@@ -1,6 +1,6 @@
1
1
  # MC Assets
2
2
 
3
- ![banner](./assets/mc-assets.png)
3
+ ![banner](./docs-assets/mc-assets.png)
4
4
 
5
5
  ```bash
6
6
  npm i mc-assets
@@ -16,8 +16,136 @@ npm i mc-assets
16
16
  - **Version Accurate** - Includes all released versions starting from 1.7.10.
17
17
  - **Memory Efficient** - Small installation size, for the fastest download & loading time.
18
18
  - **Simple & Complete API** - Works in browsers out of the box and provides parsers for all the data this library provides.
19
+ - **Easy to Use Items Textures** - Includes hand-crafted isometric textures for some blocks (50% done).
20
+ <!-- - **Models Complete** - Includes hand-crafted models for block entities. -->
19
21
 
20
- 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. Minecraft assets means block states, models info 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.
21
23
 
22
24
  > Bundled modules & block states are version-accurate starting from 1.13.0 (post-flattening) version.
23
25
  > Tested on Node.js 18 and above.
26
+
27
+
28
+ All blockstates + models + all atlas textures for all versions bundled: 4.5 MB.
29
+
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.
31
+
32
+
33
+ ## Usage
34
+
35
+ Following atlases are generated:
36
+
37
+ - Items
38
+ - Blocks
39
+ - Particles
40
+
41
+ ```ts
42
+ import { AssetsParser, getLoadedModelsStore, getLoadedBlockstatesStore } from 'mc-assets'
43
+ import blockstatesModels from 'mc-assets/dist/blockStatesModels.json'
44
+
45
+ const blockstatesStore = getLoadedBlockstatesStore(blockstatesModels)
46
+ const modelsStore = getLoadedModelsStore(blockstatesModels)
47
+ // get block states for specific block
48
+ const stoneModels = blockstatesStore.get('latest', 'stone').variants['']
49
+ // [
50
+ // { model: 'block/stone' },
51
+ // { model: 'block/stone_mirrored' },
52
+ // { model: 'block/stone', y: 180 },
53
+ // { model: 'block/stone_mirrored', y: 180 }
54
+ // ]
55
+ const model = modelsStore.get('latest', 'block/stone_mirrored')
56
+ // { parent: 'block/cube_mirrored_all', textures: { all: 'block/stone' } }
57
+
58
+ // note: you can always specify a specific version instead of 'latest'
59
+ const assetsParser = new AssetsParser('latest', blockstatesStore, modelsStore)
60
+
61
+ const resolvedModel = assetsParser.getResolvedModel({
62
+ name: 'stone', // block name not model name
63
+ properties: {},
64
+ }, false) // false (default) means return empty if variant matching properties is not found
65
+ // {
66
+ // textures: {
67
+ // all: 'block/stone',
68
+ // particle: 'block/stone',
69
+ // down: 'block/stone',
70
+ // up: 'block/stone',
71
+ // north: 'block/stone',
72
+ // east: 'block/stone',
73
+ // south: 'block/stone',
74
+ // west: 'block/stone'
75
+ // },
76
+ // elements: [
77
+ // {
78
+ // from: [ 0, 0, 0 ],
79
+ // to: [ 16, 16, 16 ],
80
+ // faces: {
81
+ // down: { texture: 'block/stone', cullface: 'down' },
82
+ // up: { texture: 'block/stone', cullface: 'up' },
83
+ // north: { texture: 'block/stone', cullface: 'north' },
84
+ // south: { texture: 'block/stone', cullface: 'south' },
85
+ // west: { texture: 'block/stone', cullface: 'west' },
86
+ // east: { texture: 'block/stone', cullface: 'east' }
87
+ // }
88
+ // }
89
+ // ]
90
+ // }
91
+ ```
92
+
93
+ ### Rendering Atlas Textures
94
+
95
+ ```ts
96
+ import { AssetsParser, AtlasParser } from 'mc-assets'
97
+ import blocksAtlases from 'mc-assets/dist/blocksAtlases.json'
98
+ import blocksAtlasLatest from 'mc-assets/dist/blocksAtlasLatest.png'
99
+ import blocksAtlasLegacy from 'mc-assets/dist/blocksAtlasLegacy.png'
100
+
101
+ const atlasParser = new AtlasParser(blocksAtlases, blocksAtlasLatest, blocksAtlasLegacy) // blocksAtlasLegacy is optional
102
+
103
+ const diamondOreTexture = atlasParser.getTextureInfo('diamond_ore', '1.13') // get old diamond ore texture
104
+ const img = await diamondOreTexture.getLoadedImage()
105
+
106
+ const canvas = document.createElement('canvas')
107
+ const sourceWidthSize = img.width * diamondOreTexture.su // 16
108
+ const sourceHeightSize = img.width * diamondOreTexture.sv // 16
109
+ canvas.width = 128
110
+ canvas.height = 128
111
+ document.body.appendChild(canvas)
112
+
113
+ const ctx = canvas.getContext('2d')!
114
+ ctx.imageSmoothingEnabled = false
115
+
116
+ ctx.drawImage(img, diamondOreTexture.u * img.width, diamondOreTexture.v * img.height, sourceWidthSize, sourceHeightSize, 0, 0, canvas.width, canvas.height)
117
+
118
+
119
+
120
+ // create new custom texture atlas (e.g. resource pack overrides)
121
+ const { atlas, canvas, newAtlasParser } = await atlasParser.makeNewAtlas('1.13') // with second argument (callback) you can override textures, with third use different tile size (use it if you have high-res textures)
122
+ const image = canvas.toDataURL() // get image data url if you need it
123
+ const diamondOreTexture = newAtlasParser.getTextureInfo('diamond_ore') // get old diamond ore texture (essentially the same as above)
124
+ ```
125
+
126
+ ### Getting Other Textures
127
+
128
+ Textures without atlases are stored in `dist/other-textures` folder. You can use them like this:
129
+
130
+ ```ts
131
+ import unmute_button from 'mc-assets/dist/other-textures/latest/gui/sprites/social_interactions/unmute_button.png'
132
+ import unmute_button_highlighted from 'mc-assets/dist/other-textures/latest/gui/sprites/social_interactions/unmute_button_highlighted.png'
133
+
134
+ const Button = styled.button`
135
+ background-image: url(${unmute_button});
136
+ background-size: cover;
137
+ background-position: center;
138
+ background-repeat: no-repeat;
139
+ border: none;
140
+ outline: none;
141
+ cursor: pointer;
142
+ width: 32px;
143
+ height: 32px;
144
+ `
145
+
146
+ const ButtonHighlighted = styled(Button)`
147
+ background-image: url(${unmute_button_highlighted});
148
+ `
149
+ ```
150
+
151
+ Play with the [atlas explorer](https://zardoy.github.io/mc-assets/).
@@ -1,5 +1,5 @@
1
1
  import { BlockModelsStore, BlockStatesStore } from './stores';
2
- import { BlockElementPos } from './types';
2
+ import { BlockElementPos, BlockModel } from './types';
3
3
  export interface QueriedBlock {
4
4
  stateId: number;
5
5
  name: string;
@@ -14,9 +14,13 @@ 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): 1 | BlockElement[];
17
+ getElementsCached(queriedBlock: QueriedBlock): number | BlockElement[];
18
18
  private resolvedModel;
19
- getElements(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): 0 | 1 | BlockElement[];
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;
20
24
  getResolvedModel(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): {
21
25
  x?: number;
22
26
  y?: number;
@@ -28,11 +32,19 @@ export declare class AssetsParser {
28
32
  elements?: {
29
33
  from: BlockElementPos;
30
34
  to: BlockElementPos;
35
+ rotation?: {
36
+ origin: [number, number, number];
37
+ axis: string;
38
+ angle: number;
39
+ rescale?: boolean;
40
+ };
31
41
  faces: {
32
42
  [name: string]: {
33
43
  texture: string;
34
44
  uv?: number[];
35
45
  cullface?: string;
46
+ rotation?: number;
47
+ tintindex?: number;
36
48
  };
37
49
  };
38
50
  }[];
@@ -43,7 +43,6 @@ export class AssetsParser {
43
43
  return properties.OR.some((or) => matchProperties(block, or));
44
44
  }
45
45
  for (const prop in properties) {
46
- // if (properties[prop] === undefined) continue // unknown property, ignore
47
46
  if (typeof properties[prop] !== 'string')
48
47
  properties[prop] = String(properties[prop]);
49
48
  if (!properties[prop].split('|').some((value) => value === String(blockProps[prop]))) {
@@ -58,7 +57,7 @@ export class AssetsParser {
58
57
  return 0;
59
58
  const states = blockStates.variants;
60
59
  if (states) {
61
- let state = states[''];
60
+ let state = states[''] || states['normal'];
62
61
  for (const key in states) {
63
62
  if (key === '')
64
63
  continue;
@@ -95,18 +94,20 @@ export class AssetsParser {
95
94
  }
96
95
  if (!modelApply)
97
96
  return 0;
98
- // const model = Array.isArray(state) ? state[Math.floor(Math.random() * state.length)] : state
99
- // TODO! not always 0
100
- const model = (Array.isArray(modelApply) ? modelApply[0] : modelApply).model;
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;
100
+ }
101
+ getResolvedModelByModelName(model, debugQueryName, clearModel = true) {
102
+ if (clearModel) {
103
+ this.resolvedModel = {};
104
+ }
101
105
  const modelData = this.blockModelsStore.get(this.version, model);
102
106
  if (!modelData)
103
- return 0;
107
+ return;
104
108
  // let textures = {} as Record<string, string>
105
109
  let elements = [];
106
110
  const resolveModel = (model) => {
107
- if (model.elements) {
108
- elements.push(...model.elements.map(({ from, to }) => [from, to]));
109
- }
110
111
  if (model.ambientocclusion !== undefined) {
111
112
  this.resolvedModel.ao = model.ambientocclusion;
112
113
  }
@@ -127,8 +128,9 @@ export class AssetsParser {
127
128
  Object.assign(this.resolvedModel.textures, model.textures);
128
129
  }
129
130
  if (model.elements) {
131
+ elements.push(...model.elements.map(({ from, to }) => [from, to]));
130
132
  this.resolvedModel.elements ??= [];
131
- this.resolvedModel.elements.push(...model.elements);
133
+ this.resolvedModel.elements.push(...structuredClone(model.elements));
132
134
  }
133
135
  if (model.parent) {
134
136
  const parent = this.blockModelsStore.get(this.version, model.parent);
@@ -147,7 +149,7 @@ export class AssetsParser {
147
149
  if (!existingKey) {
148
150
  // todo this also needs to be done at the validation stage
149
151
  // throw new Error(`Cannot resolve texture ${key} to ${value} because it is not defined`)
150
- console.warn(`${queriedBlock.name}: Cannot resolve texture ${originalTexturePath} for ${_originalKey} because it is not defined`);
152
+ console.warn(`${debugQueryName}: Cannot resolve texture ${originalTexturePath} for ${_originalKey} because it is not defined`);
151
153
  }
152
154
  else {
153
155
  return existingKey;
@@ -164,7 +166,19 @@ export class AssetsParser {
164
166
  else
165
167
  delete this.resolvedModel.textures[key];
166
168
  }
167
- return elements.length === 1 && arrEq(elements[0][0], [0, 0, 0]) && arrEq(elements[0][1], [16, 16, 16]) ? 1 : elements;
169
+ for (const elem of this.resolvedModel.elements ?? []) {
170
+ for (const [faceName, face] of Object.entries(elem.faces ?? {})) {
171
+ if (!face.texture)
172
+ continue;
173
+ // TODO validate at the validation stage
174
+ face.texture = this.resolvedModel.textures[face.texture.replace('#', '')] ?? face.texture;
175
+ }
176
+ }
177
+ // todo cleanup methods
178
+ return {
179
+ elementsOptimized: elements.length === 1 && arrEq(elements[0][0], [0, 0, 0]) && arrEq(elements[0][1], [16, 16, 16]) ? 1 : elements,
180
+ resolvedModel: this.resolvedModel,
181
+ };
168
182
  }
169
183
  getResolvedModel(queriedBlock, fallbackVariant = false) {
170
184
  this.resolvedModel = {};
@@ -178,4 +192,3 @@ export class AssetsParser {
178
192
  }
179
193
  }
180
194
  const arrEq = (a, b) => !!a && !!b && a.length === b.length && a.every((v, i) => v === b[i]);
181
- // for each element + texture
@@ -19,6 +19,10 @@ export const getAtlasSize = (numberOfTiles, tileSize) => {
19
19
  export const makeTextureAtlas = ({ input, getLoadedImage, tileSize = 16, getCanvas = () => document.createElement('canvas'), }) => {
20
20
  const tilesCount = input.length;
21
21
  const imgSize = getAtlasSize(tilesCount, tileSize).width;
22
+ const MAX_CANVAS_SIZE = 16_384;
23
+ if (imgSize > MAX_CANVAS_SIZE) {
24
+ throw new Error(`Image resolution ${imgSize} is too big, max is ${MAX_CANVAS_SIZE}x${MAX_CANVAS_SIZE}`);
25
+ }
22
26
  const canvas = getCanvas(imgSize);
23
27
  canvas.width = imgSize;
24
28
  canvas.height = imgSize;
@@ -2,6 +2,7 @@ import { VersionedStore } from './versionedStore';
2
2
  type Texture = {
3
3
  u: number;
4
4
  v: number;
5
+ tileIndex: number;
5
6
  su?: number;
6
7
  sv?: number;
7
8
  };
@@ -20,7 +21,7 @@ interface ItemsAtlases {
20
21
  legacy?: ItemsAtlasesOutputJson;
21
22
  }
22
23
  type StoreType = Texture & {
23
- imageName: string;
24
+ imageType: 'latest' | 'legacy';
24
25
  version: string;
25
26
  };
26
27
  type DataUrl = string;
@@ -33,17 +34,21 @@ export declare class AtlasParser {
33
34
  atlasHasLegacyImage: boolean;
34
35
  constructor(atlasJson: any, latestImage: string, legacyImage?: string | undefined);
35
36
  get atlas(): ItemsAtlases;
36
- getTextureInfo(version: string, itemName: string): {
37
+ getTextureInfo(itemName: string, version?: string): {
37
38
  su: number;
38
39
  sv: number;
40
+ getLoadedImage: () => Promise<HTMLImageElement>;
39
41
  u: number;
40
42
  v: number;
41
- imageName: string;
43
+ tileIndex: number;
44
+ imageType: "latest" | "legacy";
42
45
  version: string;
43
46
  } | undefined;
44
47
  makeNewAtlas(version: string, getCustomImage?: (itemName: string) => DataUrl | HTMLImageElement | boolean | void, tileSize?: number): Promise<{
45
48
  canvas: HTMLCanvasElement;
46
49
  atlas: import("./atlasCreator").JsonAtlas;
50
+ readonly newAtlasParser: AtlasParser;
51
+ readonly image: string;
47
52
  }>;
48
53
  }
49
54
  export {};
@@ -14,9 +14,9 @@ export class AtlasParser {
14
14
  this.atlasStore = new VersionedStore();
15
15
  this.atlasStore.inclusive = false;
16
16
  const itemsAtlases = atlasJson;
17
- const addByVersion = (store, version, textures, imageKey) => {
17
+ const addByVersion = (store, version, textures, imageName) => {
18
18
  for (const [key, path] of Object.entries(textures)) {
19
- store.push(version, key, { ...path, version, imageName: imageKey });
19
+ store.push(version, key, { ...path, version, imageType: imageName });
20
20
  }
21
21
  };
22
22
  addByVersion(this.atlasStore, 'latest', itemsAtlases.latest.textures, 'latest');
@@ -31,15 +31,18 @@ export class AtlasParser {
31
31
  get atlas() {
32
32
  return this.atlasJson;
33
33
  }
34
- getTextureInfo(version, itemName) {
34
+ getTextureInfo(itemName, version = 'latest') {
35
35
  const info = this.atlasStore.get(version, itemName);
36
36
  if (!info)
37
37
  return;
38
- const defaultSuSv = (info?.imageName === 'latest' ? this.atlas.latest : this.atlas.legacy).suSv;
38
+ const defaultSuSv = (info.imageType === 'latest' ? this.atlas.latest : this.atlas.legacy).suSv;
39
39
  return {
40
40
  ...info,
41
41
  su: info?.su ?? defaultSuSv,
42
42
  sv: info?.sv ?? defaultSuSv,
43
+ getLoadedImage: async () => {
44
+ return await getLoadedImage(info.imageType === 'latest' ? this.latestImage : this.legacyImage);
45
+ }
43
46
  };
44
47
  }
45
48
  async makeNewAtlas(version, getCustomImage, tileSize = this.atlas.latest.tileSize) {
@@ -62,12 +65,12 @@ export class AtlasParser {
62
65
  };
63
66
  continue;
64
67
  }
65
- const info = this.getTextureInfo(version, itemName);
68
+ const info = this.getTextureInfo(itemName, version);
66
69
  if (!info)
67
70
  throw new Error(`Missing texture info for ${itemName}`);
68
71
  const { u, v, su, sv } = info;
69
72
  const atlas = info.version === 'latest' ? itemsAtlases.latest : itemsAtlases.legacy;
70
- const image = info.imageName === 'latest' ? latestImg : legacyImg;
73
+ const image = info.imageType === 'latest' ? latestImg : legacyImg;
71
74
  if (!image)
72
75
  throw new Error(`Missing image for ${itemName}`);
73
76
  newTextures[itemName] = {
@@ -103,9 +106,19 @@ export class AtlasParser {
103
106
  },
104
107
  tileSize: this.atlas.latest.tileSize,
105
108
  });
109
+ let _newAtlasParser;
106
110
  return {
107
111
  canvas,
108
- atlas: json
112
+ atlas: json,
113
+ get newAtlasParser() {
114
+ if (!_newAtlasParser) {
115
+ _newAtlasParser = new AtlasParser({ latest: json }, canvas.toDataURL());
116
+ }
117
+ return _newAtlasParser;
118
+ },
119
+ get image() {
120
+ return canvas.toDataURL();
121
+ }
109
122
  };
110
123
  }
111
124
  }