mc-assets 0.2.34 → 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 +1 -1
- package/dist/assetsParser.d.ts +3 -0
- package/dist/assetsParser.js +48 -9
- package/dist/atlasCreator.d.ts +1 -0
- package/dist/atlasCreator.js +57 -58
- package/dist/atlasParser.d.ts +1 -0
- package/dist/atlasParser.js +107 -1
- package/dist/blocksAtlasLatest.png +0 -0
- package/dist/blocksAtlasLegacy.png +0 -0
- package/dist/blocksAtlases.json +6764 -6774
- package/dist/itemsAtlasLatest.png +0 -0
- package/dist/itemsAtlasLegacy.png +0 -0
- package/dist/itemsAtlases.json +4671 -4681
- package/dist/itemsRenderer.test.js +41 -76
- package/dist/particlesAtlasLatest.png +0 -0
- package/dist/particlesAtlasLegacy.png +0 -0
- package/dist/particlesAtlases.json +672 -682
- package/dist/worldBlockProvider.d.ts +1 -1
- package/dist/worldBlockProvider.js +41 -6
- package/package.json +2 -1
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.
|
|
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
|
|
package/dist/assetsParser.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export declare class AssetsParser {
|
|
|
18
18
|
parseProperties(properties: string): Record<string, string | boolean>;
|
|
19
19
|
getElements(queriedBlock: Omit<QueriedBlock, 'stateId'>, fallbackVariant?: boolean): 0 | 1 | BlockElement[];
|
|
20
20
|
private resolvedModel;
|
|
21
|
+
issues: string[];
|
|
22
|
+
matchedModels: string[];
|
|
23
|
+
matchedConditions: string[];
|
|
21
24
|
private getModelsByBlock;
|
|
22
25
|
getResolvedModelsByModel(model: string, debugQueryName?: string, clearModel?: boolean): {
|
|
23
26
|
resolvedModel: Pick<BlockModel, "textures" | "ao" | "elements"> & {
|
package/dist/assetsParser.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
const getNamespace = (name) => {
|
|
2
|
+
const parts = name.split(':');
|
|
3
|
+
if (parts.length === 1)
|
|
4
|
+
return 'minecraft';
|
|
5
|
+
if (parts.length === 2)
|
|
6
|
+
return parts[0];
|
|
7
|
+
return parts.slice(0, -1).join(':');
|
|
8
|
+
};
|
|
1
9
|
export class AssetsParser {
|
|
2
10
|
version;
|
|
3
11
|
blockStatesStore;
|
|
@@ -43,7 +51,12 @@ export class AssetsParser {
|
|
|
43
51
|
}
|
|
44
52
|
// looks like workaround
|
|
45
53
|
resolvedModel = {};
|
|
54
|
+
issues = [];
|
|
55
|
+
matchedModels = [];
|
|
56
|
+
matchedConditions = [];
|
|
46
57
|
getModelsByBlock(queriedBlock, fallbackVariant, multiOptim) {
|
|
58
|
+
this.matchedModels = [];
|
|
59
|
+
this.matchedConditions = [];
|
|
47
60
|
const matchProperties = (block, /* to match against */ properties) => {
|
|
48
61
|
if (!properties) {
|
|
49
62
|
return true;
|
|
@@ -69,9 +82,12 @@ export class AssetsParser {
|
|
|
69
82
|
};
|
|
70
83
|
let applyModels = [];
|
|
71
84
|
const blockStates = this.blockStatesStore.get(this.version, queriedBlock.name);
|
|
72
|
-
if (!blockStates)
|
|
85
|
+
if (!blockStates) {
|
|
86
|
+
this.issues.push(`Block ${queriedBlock.name} not found in all registered blockstates. Place it into assets/${getNamespace(queriedBlock.name)}/blockstates/${queriedBlock.name}.json`);
|
|
73
87
|
return;
|
|
88
|
+
}
|
|
74
89
|
const states = blockStates.variants;
|
|
90
|
+
let stateMatched = false;
|
|
75
91
|
if (states) {
|
|
76
92
|
let state = states[''] || states['normal'];
|
|
77
93
|
for (const key in states) {
|
|
@@ -82,9 +98,15 @@ export class AssetsParser {
|
|
|
82
98
|
break;
|
|
83
99
|
}
|
|
84
100
|
}
|
|
101
|
+
if (state) {
|
|
102
|
+
const matchedStateName = Object.entries(states).find(([key]) => state === states[key])?.[0];
|
|
103
|
+
this.matchedConditions.push(`variant:${matchedStateName}`);
|
|
104
|
+
}
|
|
85
105
|
if (!state) {
|
|
86
106
|
if (fallbackVariant) {
|
|
87
|
-
|
|
107
|
+
const firstKey = Object.keys(states)[0];
|
|
108
|
+
state = states[firstKey];
|
|
109
|
+
this.matchedConditions.push(`fallback:${firstKey}`);
|
|
88
110
|
}
|
|
89
111
|
else {
|
|
90
112
|
return;
|
|
@@ -92,12 +114,14 @@ export class AssetsParser {
|
|
|
92
114
|
}
|
|
93
115
|
if (state) {
|
|
94
116
|
applyModels.push(state);
|
|
117
|
+
stateMatched = true;
|
|
95
118
|
}
|
|
96
119
|
}
|
|
97
120
|
if (blockStates.multipart) {
|
|
98
121
|
for (const { when, apply } of blockStates.multipart) {
|
|
99
122
|
if (!when || matchProperties(queriedBlock, when)) {
|
|
100
123
|
applyModels.push(apply);
|
|
124
|
+
this.matchedConditions.push(when ? `multipart:${JSON.stringify(when)}` : 'multipart:always');
|
|
101
125
|
}
|
|
102
126
|
}
|
|
103
127
|
if (!applyModels.length && fallbackVariant) {
|
|
@@ -105,11 +129,22 @@ export class AssetsParser {
|
|
|
105
129
|
const apply = multipartWithWhen[0]?.apply;
|
|
106
130
|
if (apply) {
|
|
107
131
|
applyModels.push(apply);
|
|
132
|
+
this.matchedConditions.push('multipart:fallback');
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
}
|
|
111
|
-
if (!applyModels.length)
|
|
136
|
+
if (!applyModels.length) {
|
|
137
|
+
if (!stateMatched) {
|
|
138
|
+
const blockstatesCount = Object.keys(states ?? {}).length;
|
|
139
|
+
if (blockstatesCount) {
|
|
140
|
+
this.issues.push(`Block did not match any possible state (${blockstatesCount} possible states)`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
this.issues.push(`Blockstates for ${queriedBlock.name} are not defined`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
112
146
|
return;
|
|
147
|
+
}
|
|
113
148
|
const modelsResolved = [];
|
|
114
149
|
let part = 0;
|
|
115
150
|
for (const model of applyModels) {
|
|
@@ -141,8 +176,11 @@ export class AssetsParser {
|
|
|
141
176
|
this.resolvedModel = {};
|
|
142
177
|
}
|
|
143
178
|
const modelData = this.blockModelsStore.get(this.version, model);
|
|
144
|
-
if (!modelData)
|
|
179
|
+
if (!modelData) {
|
|
180
|
+
this.issues.push(`Model ${model} not found. Ensure it is present in assets/${getNamespace(model)}/models/${model}.json`);
|
|
145
181
|
return;
|
|
182
|
+
}
|
|
183
|
+
this.matchedModels.push(model);
|
|
146
184
|
return this.getResolvedModelsByModelData(modelData, debugQueryName, clearModel);
|
|
147
185
|
}
|
|
148
186
|
getResolvedModelsByModelData(modelData, debugQueryName, clearModel = true) {
|
|
@@ -154,8 +192,11 @@ export class AssetsParser {
|
|
|
154
192
|
collectedParentModels.push(model);
|
|
155
193
|
if (model.parent) {
|
|
156
194
|
const parent = this.blockModelsStore.get(this.version, model.parent);
|
|
157
|
-
if (!parent)
|
|
195
|
+
if (!parent) {
|
|
196
|
+
this.issues.push(`Parent model ${model.parent} not found for ${debugQueryName}`);
|
|
158
197
|
return;
|
|
198
|
+
}
|
|
199
|
+
this.matchedModels.push(`parent:${model.parent}`);
|
|
159
200
|
collectModels(parent);
|
|
160
201
|
}
|
|
161
202
|
};
|
|
@@ -192,14 +233,12 @@ export class AssetsParser {
|
|
|
192
233
|
originalTexturePath = originalTexturePath.split('/').at(-1).replace('#', '');
|
|
193
234
|
this.resolvedModel.textures ??= {};
|
|
194
235
|
if (chain.includes(originalTexturePath)) {
|
|
195
|
-
|
|
236
|
+
this.issues.push(`${debugQueryName}: Circular texture reference detected: ${chain.join(' -> ')}`);
|
|
196
237
|
return;
|
|
197
238
|
}
|
|
198
239
|
const existingKey = this.resolvedModel.textures[originalTexturePath];
|
|
199
240
|
if (!existingKey) {
|
|
200
|
-
|
|
201
|
-
// throw new Error(`Cannot resolve texture ${key} to ${value} because it is not defined`)
|
|
202
|
-
console.warn(`${debugQueryName}: Cannot resolve texture ${originalTexturePath} for ${_originalKey} because it is not defined`);
|
|
241
|
+
this.issues.push(`${debugQueryName}: Cannot resolve texture ${originalTexturePath} for ${_originalKey} because it is not defined`);
|
|
203
242
|
return;
|
|
204
243
|
}
|
|
205
244
|
else {
|
package/dist/atlasCreator.d.ts
CHANGED
package/dist/atlasCreator.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
const
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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,
|
|
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
|
-
|
|
96
|
-
const
|
|
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 {
|
package/dist/atlasParser.d.ts
CHANGED
package/dist/atlasParser.js
CHANGED
|
@@ -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
|