mc-assets 0.2.35 → 0.2.36

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
@@ -28,7 +28,7 @@ For contributing & building instructions see [building](#building) section.
28
28
  > Tested on Node.js 18 and above.
29
29
 
30
30
 
31
- All blockstates + models + all atlas textures for all versions bundled with rsbuild (uncompressed): 5.02 MB.
31
+ All blockstates + models + all atlas textures for all versions bundled with rsbuild (uncompressed): 5.15 MB.
32
32
 
33
33
  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, 1.21.1, 1.21.2, 1.21.3, 1.21.4.
34
34
 
@@ -1,3 +1,4 @@
1
+ export declare const MAX_CANVAS_SIZE = 16384;
1
2
  export declare const getAtlasSize: (numberOfTiles: number, tileSize: number) => {
2
3
  width: number;
3
4
  height: number;
@@ -1,3 +1,5 @@
1
+ import { createAtlas } from 'apl-image-packer';
2
+ export const MAX_CANVAS_SIZE = 16_384;
1
3
  function nextPowerOfTwo(n) {
2
4
  if (n === 0)
3
5
  return 1;
@@ -17,30 +19,8 @@ export const getAtlasSize = (numberOfTiles, tileSize) => {
17
19
  };
18
20
  };
19
21
  export const makeTextureAtlas = ({ input, getLoadedImage, tileSize = 16, getCanvas = (imgSize) => typeof document !== 'undefined' && document.createElement ? document.createElement('canvas') : new globalThis.Canvas(imgSize, imgSize, 'png'), }) => {
20
- const tilesCount = input.length;
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
- }
26
- const canvas = getCanvas(imgSize);
27
- canvas.width = imgSize;
28
- canvas.height = imgSize;
29
- const g = canvas.getContext('2d');
30
- g.imageSmoothingEnabled = false;
31
- const texturesIndex = {};
32
- let nextX = 0;
33
- let nextY = 0;
34
- let rowMaxY = 0;
35
- const goToNextRow = () => {
36
- nextX = 0;
37
- nextY += Math.ceil(rowMaxY / tileSize) * tileSize;
38
- rowMaxY = 0;
39
- };
40
- const suSv = tileSize / imgSize;
41
- const tilesPerRow = Math.ceil(imgSize / tileSize);
42
- for (const i in input) {
43
- const keyValue = input[i];
22
+ // Pre-calculate all texture dimensions and prepare images
23
+ const texturesWithDimensions = input.map(keyValue => {
44
24
  const inputData = getLoadedImage(keyValue);
45
25
  let img;
46
26
  if (inputData.image) {
@@ -53,57 +33,76 @@ export const makeTextureAtlas = ({ input, getLoadedImage, tileSize = 16, getCanv
53
33
  else {
54
34
  throw new Error('No image or contents');
55
35
  }
56
- let su = suSv;
57
- let sv = suSv;
58
36
  let renderWidth = tileSize * (inputData.tileWidthMult ?? 1);
59
37
  let renderHeight = tileSize;
60
38
  if (inputData.useOriginalSize || inputData.renderWidth || inputData.renderHeight) {
61
39
  const texWidth = inputData.renderWidth ?? img.width;
62
40
  const texHeight = inputData.renderHeight ?? img.height;
63
- // todo check have enough space
64
41
  renderWidth = Math.ceil(texWidth / tileSize) * tileSize;
65
42
  renderHeight = Math.ceil(texHeight / tileSize) * tileSize;
66
- su = texWidth / imgSize;
67
- sv = texHeight / imgSize;
68
- // renderWidth and renderHeight take full tile size so everything is aligned to the grid
69
- if (renderHeight > imgSize || renderWidth > imgSize) {
70
- throw new Error('Texture ' + keyValue + ' is too big');
71
- }
72
- }
73
- if (nextX + renderWidth > imgSize) {
74
- goToNextRow();
75
43
  }
76
- const x = nextX;
77
- const y = nextY;
78
- const yIndex = y / tileSize;
79
- const xIndex = x / tileSize;
44
+ return {
45
+ keyValue,
46
+ img,
47
+ inputData,
48
+ renderWidth,
49
+ renderHeight,
50
+ renderSourceWidth: inputData.useOriginalSize ? img.width : inputData.renderSourceWidth ?? Math.min(img.width, img.height),
51
+ renderSourceHeight: inputData.useOriginalSize ? img.height : inputData.renderSourceHeight ?? Math.min(img.width, img.height),
52
+ renderSourceStartX: inputData.renderSourceStartX ?? 0,
53
+ renderSourceStartY: inputData.renderSourceStartY ?? 0,
54
+ };
55
+ });
56
+ // Use apl-image-packer to calculate optimal positions
57
+ const atlas = createAtlas(texturesWithDimensions.map(tex => ({
58
+ width: tex.renderWidth,
59
+ height: tex.renderHeight,
60
+ data: tex // Store all texture data for later use
61
+ })));
62
+ // Round up atlas size to power of 2
63
+ const imgSize = Math.max(nextPowerOfTwo(atlas.width), nextPowerOfTwo(atlas.height));
64
+ if (imgSize > MAX_CANVAS_SIZE) {
65
+ const sizeGroups = texturesWithDimensions.reduce((acc, t) => {
66
+ const key = `${t.renderWidth}x${t.renderHeight}`;
67
+ acc[key] = (acc[key] || 0) + 1;
68
+ return acc;
69
+ }, {});
70
+ const sizeGroupsStr = Object.entries(sizeGroups)
71
+ .sort(([, a], [, b]) => b - a)
72
+ .map(([size, count]) => `${size}(${count})`)
73
+ .join(', ');
74
+ throw new Error(`Required atlas size ${imgSize} exceeds maximum ${MAX_CANVAS_SIZE}. Texture sizes: ${sizeGroupsStr}`);
75
+ }
76
+ const canvas = getCanvas(imgSize);
77
+ canvas.width = imgSize;
78
+ canvas.height = imgSize;
79
+ const g = canvas.getContext('2d');
80
+ g.imageSmoothingEnabled = false;
81
+ const texturesIndex = {};
82
+ const suSv = tileSize / imgSize;
83
+ const tilesPerRow = Math.ceil(imgSize / tileSize);
84
+ // Draw textures at their calculated positions
85
+ for (const coord of atlas.coords) {
86
+ const tex = coord.img.data;
87
+ const x = coord.x;
88
+ const y = coord.y;
89
+ const yIndex = Math.floor(y / tileSize);
90
+ const xIndex = Math.floor(x / tileSize);
80
91
  const tileIndex = yIndex * tilesPerRow + xIndex;
81
- nextX += renderWidth;
82
- rowMaxY = Math.max(rowMaxY, renderHeight);
83
- if (nextX >= imgSize) {
84
- goToNextRow();
85
- }
86
- const renderSourceDefaultSize = Math.min(img.width, img.height);
87
- const renderSourceWidth = inputData.useOriginalSize ? img.width : inputData.renderSourceWidth ?? renderSourceDefaultSize;
88
- const renderSourceHeight = inputData.useOriginalSize ? img.height : inputData.renderSourceHeight ?? renderSourceDefaultSize;
89
92
  try {
90
- g.drawImage(img, inputData.renderSourceStartX ?? 0, inputData.renderSourceStartY ?? 0, renderSourceWidth, renderSourceHeight, x, y, renderWidth, renderHeight);
93
+ g.drawImage(tex.img, tex.renderSourceStartX, tex.renderSourceStartY, tex.renderSourceWidth, tex.renderSourceHeight, x, y, tex.renderWidth, tex.renderHeight);
91
94
  }
92
95
  catch (err) {
93
- throw new Error(`Error drawing ${keyValue}: ${err}`);
96
+ throw new Error(`Error drawing ${tex.keyValue}: ${err}`);
94
97
  }
95
- // remove the extension eg .png
96
- const cleanName = keyValue.split('.').slice(0, -1).join('.') || keyValue;
98
+ const cleanName = tex.keyValue.split('.').slice(0, -1).join('.') || tex.keyValue;
99
+ const su = tex.renderWidth / imgSize;
100
+ const sv = tex.renderHeight / imgSize;
97
101
  texturesIndex[cleanName] = {
98
102
  u: x / imgSize,
99
103
  v: y / imgSize,
100
104
  tileIndex,
101
- ...su == suSv && sv == suSv ? {} : {
102
- su,
103
- sv,
104
- // width: renderWidth,
105
- // height: renderHeight
106
- }
105
+ ...su == suSv && sv == suSv ? {} : { su, sv }
107
106
  };
108
107
  }
109
108
  return {
@@ -49,5 +49,6 @@ export declare class AtlasParser {
49
49
  readonly newAtlasParser: AtlasParser;
50
50
  readonly image: string;
51
51
  }>;
52
+ createDebugImage(writeNames?: boolean): Promise<string>;
52
53
  }
53
54
  export {};
@@ -1,5 +1,5 @@
1
1
  import { VersionedStore } from './versionedStore';
2
- import { makeTextureAtlas } from './atlasCreator';
2
+ import { makeTextureAtlas, MAX_CANVAS_SIZE } from './atlasCreator';
3
3
  import { getLoadedImage } from './utils';
4
4
  export class AtlasParser {
5
5
  atlasJson;
@@ -122,4 +122,110 @@ export class AtlasParser {
122
122
  }
123
123
  };
124
124
  }
125
+ async createDebugImage(writeNames = false) {
126
+ const atlas = this.atlas.latest;
127
+ if (atlas.width !== atlas.height) {
128
+ throw new Error('Atlas must be square');
129
+ }
130
+ const wantedSize = Math.min(MAX_CANVAS_SIZE, atlas.width * (writeNames ? 6 : 1));
131
+ const scale = wantedSize / atlas.width;
132
+ const height = atlas.height * scale;
133
+ const width = atlas.width * scale;
134
+ const canvas = globalThis.Canvas ? new globalThis.Canvas(width, height) : document.createElement('canvas');
135
+ canvas.width = width;
136
+ canvas.height = height;
137
+ const ctx = canvas.getContext('2d');
138
+ // Disable image smoothing for pixelated rendering
139
+ ctx.imageSmoothingEnabled = false;
140
+ // For older browsers
141
+ //@ts-ignore
142
+ ctx.webkitImageSmoothingEnabled = false;
143
+ //@ts-ignore
144
+ ctx.mozImageSmoothingEnabled = false;
145
+ //@ts-ignore
146
+ ctx.msImageSmoothingEnabled = false;
147
+ // Draw the base atlas image
148
+ const img = await getLoadedImage(this.latestImage);
149
+ ctx.drawImage(img, 0, 0, atlas.width, atlas.height, 0, 0, width, height);
150
+ // Draw debug rectangles for each texture
151
+ ctx.strokeStyle = '#ff0000';
152
+ ctx.lineWidth = 2;
153
+ const textureNames = Object.keys(atlas.textures);
154
+ const totalTextures = textureNames.length;
155
+ let lastProgress = 0;
156
+ textureNames.forEach((textureName, i) => {
157
+ const texture = atlas.textures[textureName];
158
+ // Log progress every 10%
159
+ const progress = Math.floor((i / totalTextures) * 100);
160
+ if (progress >= lastProgress + 10) {
161
+ console.log(`Processing textures: ${progress}% (${i}/${totalTextures})`);
162
+ lastProgress = progress;
163
+ }
164
+ const x = texture.u * atlas.width * scale;
165
+ const y = texture.v * atlas.height * scale;
166
+ const width = (texture.su || atlas.suSv) * atlas.width * scale;
167
+ const height = (texture.sv || atlas.suSv) * atlas.height * scale;
168
+ // Create striped pattern
169
+ const pattern = ctx.createPattern((() => {
170
+ const patternCanvas = globalThis.Canvas ? new globalThis.Canvas(10, 10) : document.createElement('canvas');
171
+ patternCanvas.width = 10;
172
+ patternCanvas.height = 10;
173
+ const patternCtx = patternCanvas.getContext('2d');
174
+ patternCtx.fillStyle = '#ff0000';
175
+ patternCtx.fillRect(0, 0, 5, 10);
176
+ patternCtx.fillStyle = '#ffff00';
177
+ patternCtx.fillRect(5, 0, 5, 10);
178
+ return patternCanvas;
179
+ })(), 'repeat');
180
+ ctx.strokeStyle = pattern;
181
+ ctx.strokeRect(x, y, width, height);
182
+ if (writeNames) {
183
+ // Configure text style
184
+ const text = textureName;
185
+ const padding = 4;
186
+ const maxWidth = width - padding * 2;
187
+ // Start with a relatively large font size and decrease until text fits
188
+ let fontSize = 12;
189
+ do {
190
+ ctx.font = `${fontSize}px monospace`;
191
+ const metrics = ctx.measureText(text);
192
+ if (metrics.width <= maxWidth || fontSize <= 6) {
193
+ break;
194
+ }
195
+ fontSize -= 1;
196
+ } while (fontSize > 6);
197
+ ctx.fillStyle = 'white';
198
+ ctx.strokeStyle = 'black';
199
+ ctx.lineWidth = Math.max(1, fontSize / 6); // Scale outline with font size
200
+ ctx.textBaseline = 'top';
201
+ // Draw text with outline for better visibility
202
+ const textX = x + padding;
203
+ const textY = y + padding;
204
+ // Split text into lines if it's still too wide
205
+ const words = text.split(/(?=[A-Z_/])/g);
206
+ let line = '';
207
+ let lines = [];
208
+ for (const word of words) {
209
+ const testLine = line + word;
210
+ const metrics = ctx.measureText(testLine);
211
+ if (metrics.width > maxWidth && line !== '') {
212
+ lines.push(line);
213
+ line = word;
214
+ }
215
+ else {
216
+ line = testLine;
217
+ }
218
+ }
219
+ lines.push(line);
220
+ // Draw each line
221
+ const lineHeight = fontSize * 1.2;
222
+ lines.forEach((line, i) => {
223
+ ctx.strokeText(line, textX, textY + i * lineHeight);
224
+ ctx.fillText(line, textX, textY + i * lineHeight);
225
+ });
226
+ }
227
+ });
228
+ console.log(`Processing textures: 100% (${totalTextures}/${totalTextures})`);
229
+ return canvas.toDataURL();
230
+ }
125
231
  }
Binary file
Binary file