babylonjs-editor-cli 5.0.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/bin/babylonjs-editor-cli.js +3 -0
- package/build/index.node.js +21 -0
- package/build/package.json +34 -0
- package/build/src/export.mjs +2 -0
- package/build/src/export.mjs.map +1 -0
- package/build/src/index.mjs +22 -0
- package/build/src/index.mjs.map +1 -0
- package/build/src/pack/assets/assets.mjs +26 -0
- package/build/src/pack/assets/assets.mjs.map +1 -0
- package/build/src/pack/assets/ktx.mjs +121 -0
- package/build/src/pack/assets/ktx.mjs.map +1 -0
- package/build/src/pack/assets/material.mjs +53 -0
- package/build/src/pack/assets/material.mjs.map +1 -0
- package/build/src/pack/assets/particle-system.mjs +75 -0
- package/build/src/pack/assets/particle-system.mjs.map +1 -0
- package/build/src/pack/assets/process.mjs +78 -0
- package/build/src/pack/assets/process.mjs.map +1 -0
- package/build/src/pack/assets/texture.mjs +61 -0
- package/build/src/pack/assets/texture.mjs.map +1 -0
- package/build/src/pack/geometry.mjs +19 -0
- package/build/src/pack/geometry.mjs.map +1 -0
- package/build/src/pack/pack.mjs +127 -0
- package/build/src/pack/pack.mjs.map +1 -0
- package/build/src/pack/scene.mjs +241 -0
- package/build/src/pack/scene.mjs.map +1 -0
- package/build/src/tools/extract.mjs +51 -0
- package/build/src/tools/extract.mjs.map +1 -0
- package/build/src/tools/fs.mjs +9 -0
- package/build/src/tools/fs.mjs.map +1 -0
- package/build/src/tools/ktx.mjs +36 -0
- package/build/src/tools/ktx.mjs.map +1 -0
- package/build/src/tools/process.mjs +21 -0
- package/build/src/tools/process.mjs.map +1 -0
- package/build/src/tools/scalar.mjs +23 -0
- package/build/src/tools/scalar.mjs.map +1 -0
- package/build/src/tools/scene.mjs +68 -0
- package/build/src/tools/scene.mjs.map +1 -0
- package/build/src/tools/worker.mjs +35 -0
- package/build/src/tools/worker.mjs.map +1 -0
- package/build/src/tools/workers/md5.mjs +10 -0
- package/build/src/tools/workers/md5.mjs.map +1 -0
- package/declaration/src/export.d.mts +1 -0
- package/declaration/src/index.d.mts +1 -0
- package/declaration/src/pack/assets/assets.d.mts +11 -0
- package/declaration/src/pack/assets/ktx.d.mts +28 -0
- package/declaration/src/pack/assets/material.d.mts +11 -0
- package/declaration/src/pack/assets/particle-system.d.mts +12 -0
- package/declaration/src/pack/assets/process.d.mts +9 -0
- package/declaration/src/pack/assets/texture.d.mts +7 -0
- package/declaration/src/pack/geometry.d.mts +10 -0
- package/declaration/src/pack/pack.d.mts +5 -0
- package/declaration/src/pack/scene.d.mts +13 -0
- package/declaration/src/tools/extract.d.mts +8 -0
- package/declaration/src/tools/fs.d.mts +2 -0
- package/declaration/src/tools/ktx.d.mts +3 -0
- package/declaration/src/tools/process.d.mts +5 -0
- package/declaration/src/tools/scalar.d.mts +8 -0
- package/declaration/src/tools/scene.d.mts +21 -0
- package/declaration/src/tools/worker.d.mts +18 -0
- package/declaration/src/tools/workers/md5.d.mts +1 -0
- package/esbuild.mjs +57 -0
- package/package.json +34 -0
- package/src/export.mts +1 -0
- package/src/index.mts +28 -0
- package/src/pack/assets/assets.mts +46 -0
- package/src/pack/assets/ktx.mts +158 -0
- package/src/pack/assets/material.mts +86 -0
- package/src/pack/assets/particle-system.mts +109 -0
- package/src/pack/assets/process.mts +102 -0
- package/src/pack/assets/texture.mts +91 -0
- package/src/pack/geometry.mts +38 -0
- package/src/pack/pack.mts +159 -0
- package/src/pack/scene.mts +346 -0
- package/src/tools/extract.mts +74 -0
- package/src/tools/fs.mts +11 -0
- package/src/tools/ktx.mts +44 -0
- package/src/tools/process.mts +21 -0
- package/src/tools/scalar.mts +28 -0
- package/src/tools/scene.mts +90 -0
- package/src/tools/worker.mts +39 -0
- package/src/tools/workers/md5.mts +13 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { join, basename } from "node:path/posix";
|
|
2
|
+
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
|
|
5
|
+
import { readSceneDirectories } from "../tools/scene.mjs";
|
|
6
|
+
|
|
7
|
+
import { compressFileToKtx } from "./assets/ktx.mjs";
|
|
8
|
+
import { extractNodeMaterialTextures } from "./assets/material.mjs";
|
|
9
|
+
import { getExtractedTextureOutputPath } from "./assets/texture.mjs";
|
|
10
|
+
import { extractNodeParticleSystemSetTextures, extractParticleSystemTextures } from "./assets/particle-system.mjs";
|
|
11
|
+
|
|
12
|
+
export interface ICreateBabylonSceneOptions {
|
|
13
|
+
sceneFile: string;
|
|
14
|
+
sceneName: string;
|
|
15
|
+
publicDir: string;
|
|
16
|
+
babylonjsEditorToolsVersion: string;
|
|
17
|
+
exportedAssets: string[];
|
|
18
|
+
optimize: boolean;
|
|
19
|
+
compressedTexturesEnabled: boolean;
|
|
20
|
+
|
|
21
|
+
config: any;
|
|
22
|
+
directories: Awaited<ReturnType<typeof readSceneDirectories>>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function createBabylonScene(options: ICreateBabylonSceneOptions) {
|
|
26
|
+
const meshes: any[] = [];
|
|
27
|
+
const materials: any[] = [];
|
|
28
|
+
const transformNodes: any[] = [];
|
|
29
|
+
const morphTargetManagers: any[] = [];
|
|
30
|
+
|
|
31
|
+
// Meshes
|
|
32
|
+
await Promise.all(
|
|
33
|
+
options.directories.meshesFiles.map(async (file) => {
|
|
34
|
+
const data = await fs.readJSON(join(options.sceneFile, "meshes", file));
|
|
35
|
+
const mesh = data.meshes[0];
|
|
36
|
+
|
|
37
|
+
if (mesh.delayLoadingFile) {
|
|
38
|
+
mesh.delayLoadingFile = join(options.sceneName, basename(mesh.delayLoadingFile));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (mesh.metadata?.parentId) {
|
|
42
|
+
mesh.parentId = mesh.metadata.parentId;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
mesh.instances?.forEach((instance) => {
|
|
46
|
+
if (instance.metadata?.parentId) {
|
|
47
|
+
instance.parentId = instance.metadata.parentId;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (data.basePoseMatrix) {
|
|
52
|
+
mesh.basePoseMatrix = data.basePoseMatrix;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
meshes.push(mesh);
|
|
56
|
+
|
|
57
|
+
data.materials?.forEach((material) => {
|
|
58
|
+
const existingMaterial = materials.find((m) => m.id === material.id);
|
|
59
|
+
if (!existingMaterial) {
|
|
60
|
+
materials.push(material);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Morph targets
|
|
67
|
+
await Promise.all(
|
|
68
|
+
options.directories.morphTargetManagerFiles.map(async (file) => {
|
|
69
|
+
const data = await fs.readJSON(join(options.sceneFile, "morphTargetManagers", file));
|
|
70
|
+
|
|
71
|
+
if (options.babylonjsEditorToolsVersion >= "5.2.6") {
|
|
72
|
+
data.targets.forEach((target) => {
|
|
73
|
+
if (target.delayLoadingFile) {
|
|
74
|
+
target.delayLoadingFile = join(options.sceneName, "morphTargets", basename(target.delayLoadingFile));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
await Promise.all(
|
|
79
|
+
data.targets.map(async (target) => {
|
|
80
|
+
const binaryFileData = join(options.sceneFile, "morphTargets", basename(target.delayLoadingFile));
|
|
81
|
+
const buffer = (await fs.readFile(binaryFileData)).buffer;
|
|
82
|
+
|
|
83
|
+
if (target.positionsCount) {
|
|
84
|
+
target.positions = Array.prototype.slice.call(new Float32Array(buffer, target.positionsOffset, target.positionsCount));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (target.normalsCount) {
|
|
88
|
+
target.normals = Array.prototype.slice.call(new Float32Array(buffer, target.normalsOffset, target.normalsCount));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (target.tangentsCount) {
|
|
92
|
+
target.tangents = Array.prototype.slice.call(new Float32Array(buffer, target.tangentsOffset, target.tangentsCount));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (target.uvsCount) {
|
|
96
|
+
target.uvs = Array.prototype.slice.call(new Float32Array(buffer, target.uvsOffset, target.uvsCount));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (target.uv2sCount) {
|
|
100
|
+
target.uv2s = Array.prototype.slice.call(new Float32Array(buffer, target.uv2sOffset, target.uv2sCount));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
delete target.delayLoadingFile;
|
|
104
|
+
|
|
105
|
+
delete target.positionsCount;
|
|
106
|
+
delete target.normalsCount;
|
|
107
|
+
delete target.tangentsCount;
|
|
108
|
+
delete target.uvsCount;
|
|
109
|
+
delete target.uv2sCount;
|
|
110
|
+
|
|
111
|
+
delete target.positionsOffset;
|
|
112
|
+
delete target.normalsOffset;
|
|
113
|
+
delete target.tangentsOffset;
|
|
114
|
+
delete target.uvsOffset;
|
|
115
|
+
delete target.uv2sOffset;
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
morphTargetManagers.push(data);
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Transform nodes
|
|
125
|
+
await Promise.all(
|
|
126
|
+
options.directories.nodesFiles.map(async (file) => {
|
|
127
|
+
const data = await fs.readJSON(join(options.sceneFile, "nodes", file));
|
|
128
|
+
if (data.metadata?.parentId) {
|
|
129
|
+
data.parentId = data.metadata.parentId;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
transformNodes.push(data);
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
await Promise.all(
|
|
137
|
+
options.directories.spriteManagerFiles.map(async (file) => {
|
|
138
|
+
const data = await fs.readJSON(join(options.sceneFile, "sprite-managers", file));
|
|
139
|
+
if (data.metadata?.parentId) {
|
|
140
|
+
data.parentId = data.metadata.parentId;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
transformNodes.push(data);
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
await Promise.all(
|
|
148
|
+
options.directories.spriteMapFiles.map(async (file) => {
|
|
149
|
+
const data = await fs.readJSON(join(options.sceneFile, "sprite-maps", file));
|
|
150
|
+
if (data.metadata?.parentId) {
|
|
151
|
+
data.parentId = data.metadata.parentId;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
transformNodes.push(data);
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Extract materials
|
|
159
|
+
const extractedTexturesOutputPath = getExtractedTextureOutputPath(options.publicDir);
|
|
160
|
+
await fs.ensureDir(extractedTexturesOutputPath);
|
|
161
|
+
|
|
162
|
+
await Promise.all(
|
|
163
|
+
materials.map(async (material) => {
|
|
164
|
+
if (material.customType === "BABYLON.NodeMaterial") {
|
|
165
|
+
const result = await extractNodeMaterialTextures(material, {
|
|
166
|
+
extractedTexturesOutputPath,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await Promise.all(
|
|
170
|
+
result.map(async (relativePath) => {
|
|
171
|
+
const finalPath = join(options.publicDir, relativePath);
|
|
172
|
+
options.exportedAssets.push(finalPath);
|
|
173
|
+
|
|
174
|
+
if (options.optimize && options.compressedTexturesEnabled) {
|
|
175
|
+
await compressFileToKtx(finalPath, {
|
|
176
|
+
...options,
|
|
177
|
+
force: false,
|
|
178
|
+
exportedAssets: options.exportedAssets,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Particle systems
|
|
188
|
+
const particleSystems = await Promise.all(
|
|
189
|
+
options.directories.particleSystemFiles.map(async (file) => {
|
|
190
|
+
const data = await fs.readJSON(join(options.sceneFile, "particleSystems", file));
|
|
191
|
+
|
|
192
|
+
const result = await extractParticleSystemTextures(data, {
|
|
193
|
+
extractedTexturesOutputPath,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (result) {
|
|
197
|
+
const finalPath = join(options.publicDir, result);
|
|
198
|
+
options.exportedAssets.push(finalPath);
|
|
199
|
+
|
|
200
|
+
if (options.optimize && options.compressedTexturesEnabled) {
|
|
201
|
+
await compressFileToKtx(finalPath, {
|
|
202
|
+
...options,
|
|
203
|
+
force: false,
|
|
204
|
+
exportedAssets: options.exportedAssets,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return data;
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Node particle system sets
|
|
214
|
+
await Promise.all(
|
|
215
|
+
options.directories.nodeParticleSystemSetFiles.map(async (file) => {
|
|
216
|
+
const data = await fs.readJSON(join(options.sceneFile, "nodeParticleSystemSets", file));
|
|
217
|
+
|
|
218
|
+
if (data.metadata?.parentId) {
|
|
219
|
+
data.parentId = data.metadata.parentId;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (data.nodeParticleSystemSet) {
|
|
223
|
+
const result = await extractNodeParticleSystemSetTextures(data.nodeParticleSystemSet, {
|
|
224
|
+
extractedTexturesOutputPath,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await Promise.all(
|
|
228
|
+
result.map(async (relativePath) => {
|
|
229
|
+
const finalPath = join(options.publicDir, relativePath);
|
|
230
|
+
options.exportedAssets.push(finalPath);
|
|
231
|
+
|
|
232
|
+
if (options.optimize && options.compressedTexturesEnabled) {
|
|
233
|
+
await compressFileToKtx(finalPath, {
|
|
234
|
+
...options,
|
|
235
|
+
force: false,
|
|
236
|
+
exportedAssets: options.exportedAssets,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
meshes.push(data);
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const scene = {
|
|
248
|
+
autoClear: true,
|
|
249
|
+
physicsEnabled: true,
|
|
250
|
+
collisionsEnabled: true,
|
|
251
|
+
useRightHandedSystem: false,
|
|
252
|
+
physicsEngine: "HavokPlugin",
|
|
253
|
+
iblIntensity: 1,
|
|
254
|
+
|
|
255
|
+
clearColor: options.config.clearColor,
|
|
256
|
+
ambientColor: options.config.ambientColor,
|
|
257
|
+
|
|
258
|
+
geometries: {
|
|
259
|
+
boxes: [],
|
|
260
|
+
spheres: [],
|
|
261
|
+
cylinders: [],
|
|
262
|
+
toruses: [],
|
|
263
|
+
grounds: [],
|
|
264
|
+
planes: [],
|
|
265
|
+
torusKnots: [],
|
|
266
|
+
vertexData: [],
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
gravity: options.config.gravity ?? [0, -9.81, 0],
|
|
270
|
+
|
|
271
|
+
fogColor: options.config.fog.fogColor,
|
|
272
|
+
fogStart: options.config.fog.fogStart,
|
|
273
|
+
fogEnd: options.config.fog.fogEnd,
|
|
274
|
+
fogDensity: options.config.fog.fogDensity,
|
|
275
|
+
fogMode: options.config.fog.fogMode,
|
|
276
|
+
|
|
277
|
+
physicsGravity: options.config.physics.gravity,
|
|
278
|
+
|
|
279
|
+
environmentIntensity: options.config.environment.environmentIntensity,
|
|
280
|
+
environmentTexture: options.config.environment.environmentTexture,
|
|
281
|
+
|
|
282
|
+
metadata: options.config.metadata,
|
|
283
|
+
|
|
284
|
+
animations: options.config.animations,
|
|
285
|
+
|
|
286
|
+
postProcesses: [],
|
|
287
|
+
multiMaterials: [],
|
|
288
|
+
spriteManagers: [],
|
|
289
|
+
reflectionProbes: [],
|
|
290
|
+
|
|
291
|
+
meshes,
|
|
292
|
+
materials,
|
|
293
|
+
morphTargetManagers,
|
|
294
|
+
transformNodes,
|
|
295
|
+
particleSystems,
|
|
296
|
+
|
|
297
|
+
animationGroups: await Promise.all(
|
|
298
|
+
options.directories.animationGroupFiles.map(async (file) => {
|
|
299
|
+
return fs.readJSON(join(options.sceneFile, "animationGroups", file));
|
|
300
|
+
})
|
|
301
|
+
),
|
|
302
|
+
skeletons: await Promise.all(
|
|
303
|
+
options.directories.skeletonFiles.map(async (file) => {
|
|
304
|
+
return fs.readJSON(join(options.sceneFile, "skeletons", file));
|
|
305
|
+
})
|
|
306
|
+
),
|
|
307
|
+
cameras: await Promise.all(
|
|
308
|
+
options.directories.cameraFiles.map(async (file) => {
|
|
309
|
+
const data = await fs.readJSON(join(options.sceneFile, "cameras", file));
|
|
310
|
+
if (data.metadata?.parentId) {
|
|
311
|
+
data.parentId = data.metadata.parentId;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return data;
|
|
315
|
+
})
|
|
316
|
+
),
|
|
317
|
+
lights: await Promise.all(
|
|
318
|
+
options.directories.lightsFiles.map(async (file) => {
|
|
319
|
+
const data = await fs.readJSON(join(options.sceneFile, "lights", file));
|
|
320
|
+
if (data.metadata?.parentId) {
|
|
321
|
+
data.parentId = data.metadata.parentId;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return data;
|
|
325
|
+
})
|
|
326
|
+
),
|
|
327
|
+
shadowGenerators: await Promise.all(
|
|
328
|
+
options.directories.shadowGeneratorFiles.map(async (file) => {
|
|
329
|
+
return fs.readJSON(join(options.sceneFile, "shadowGenerators", file));
|
|
330
|
+
})
|
|
331
|
+
),
|
|
332
|
+
sounds: await Promise.all(
|
|
333
|
+
options.directories.soundFiles.map(async (file) => {
|
|
334
|
+
return fs.readJSON(join(options.sceneFile, "sounds", file));
|
|
335
|
+
})
|
|
336
|
+
),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const destination = join(options.publicDir, `${options.sceneName}.babylon`);
|
|
340
|
+
await fs.writeJSON(destination, scene, {
|
|
341
|
+
encoding: "utf-8",
|
|
342
|
+
// spaces: "\t", // Useful for debug
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
options.exportedAssets.push(destination);
|
|
346
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { join } from "node:path/posix";
|
|
2
|
+
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
|
|
6
|
+
import { executeSimpleWorker } from "./worker.mjs";
|
|
7
|
+
|
|
8
|
+
export interface IExtractTextureAssetFromDataStringOptions {
|
|
9
|
+
extractedTexturesOutputPath: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function extractTextureAssetFromDataString(dataString: string, options: IExtractTextureAssetFromDataStringOptions) {
|
|
13
|
+
const split = dataString.split(",");
|
|
14
|
+
const header = split[0];
|
|
15
|
+
|
|
16
|
+
const headerSplit = header.split(";");
|
|
17
|
+
const dataType = headerSplit[1];
|
|
18
|
+
|
|
19
|
+
const byteString = split[1];
|
|
20
|
+
|
|
21
|
+
if (dataType === "base64") {
|
|
22
|
+
const buffer = Buffer.from(byteString, "base64");
|
|
23
|
+
const image = sharp(buffer);
|
|
24
|
+
|
|
25
|
+
const [metadata, hash] = await Promise.all([image.metadata(), executeSimpleWorker("workers/md5.mjs", buffer)]);
|
|
26
|
+
|
|
27
|
+
let filename: string;
|
|
28
|
+
switch (metadata.format) {
|
|
29
|
+
case "png":
|
|
30
|
+
filename = `${hash}.png`;
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case "jpg":
|
|
34
|
+
case "jpeg":
|
|
35
|
+
filename = `${hash}.jpg`;
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
default:
|
|
39
|
+
console.error(`Unsupported embedded texture format while extracting texture: ${metadata.format}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const outputFilename = join(options.extractedTexturesOutputPath, filename);
|
|
44
|
+
if (!(await fs.pathExists(outputFilename))) {
|
|
45
|
+
await fs.writeFile(outputFilename, buffer);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return join("assets", "editor-generated_extracted-textures", filename).replace(/\\/g, "/");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface IExtractTextureAssetFromUrlOptions {
|
|
55
|
+
extractedTexturesOutputPath: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function extractTextureAssetFromUrl(url: string, options: IExtractTextureAssetFromUrlOptions) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(url);
|
|
61
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
62
|
+
|
|
63
|
+
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
64
|
+
const dataString = `data:${response.headers.get("content-type") ?? "image/unknown"};base64,${base64}`;
|
|
65
|
+
|
|
66
|
+
return extractTextureAssetFromDataString(dataString, {
|
|
67
|
+
...options,
|
|
68
|
+
});
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(`Failed to extract texture from url "${url}": ${e.message}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
package/src/tools/fs.mts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
|
|
3
|
+
export async function normalizedGlob(...args: Parameters<typeof glob>) {
|
|
4
|
+
const result = await glob(...args);
|
|
5
|
+
|
|
6
|
+
result.forEach((_: unknown, index: number) => {
|
|
7
|
+
result[index] = result[index].toString().replace(/\\/g, "/");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
return result as string[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { normalizedGlob } from "./fs.mjs";
|
|
3
|
+
import { join } from "node:path/posix";
|
|
4
|
+
|
|
5
|
+
export let pvrTexToolAbsolutePath: string | undefined;
|
|
6
|
+
|
|
7
|
+
export function setPVRTexToolAbsolutePath(absolutePath: string) {
|
|
8
|
+
pvrTexToolAbsolutePath = absolutePath;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function locatePVRTexTool() {
|
|
12
|
+
if (process.env.PVRTexToolCLI) {
|
|
13
|
+
return (pvrTexToolAbsolutePath = process.env.PVRTexToolCLI);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const platform = os.platform();
|
|
17
|
+
|
|
18
|
+
let absolutePathToCheck: string | null = null;
|
|
19
|
+
switch (platform) {
|
|
20
|
+
case "darwin":
|
|
21
|
+
absolutePathToCheck = "/Applications/Imagination/";
|
|
22
|
+
case "win32":
|
|
23
|
+
absolutePathToCheck = "C:/Imagination Technologies/";
|
|
24
|
+
default:
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!absolutePathToCheck) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const files = await normalizedGlob(join(absolutePathToCheck, "**"), {
|
|
33
|
+
nodir: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
switch (platform) {
|
|
37
|
+
case "darwin":
|
|
38
|
+
return (pvrTexToolAbsolutePath = files.find((file) => file.endsWith("macOS/PVRTexToolCLI")));
|
|
39
|
+
case "win32":
|
|
40
|
+
return (pvrTexToolAbsolutePath = files.find((file) => file.endsWith("Windows_x86_64/PVRTexToolCLI.exe")));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Executes the given command asynchronously using `child_process`
|
|
7
|
+
* @param command defines the command to execute.
|
|
8
|
+
*/
|
|
9
|
+
export function executeAsync(command: string): Promise<void> {
|
|
10
|
+
return new Promise<void>((resolve, reject) => {
|
|
11
|
+
exec(command, (error, stdout, stderr) => {
|
|
12
|
+
if (error) {
|
|
13
|
+
console.error(chalk.red(command), chalk.red(stderr));
|
|
14
|
+
reject(error);
|
|
15
|
+
} else {
|
|
16
|
+
console.log(chalk.gray(command), chalk.gray(stdout));
|
|
17
|
+
resolve();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the power of two sizes until the given limit.
|
|
3
|
+
* @param limit The limit size.
|
|
4
|
+
* @param from The starting size. Default is 1.
|
|
5
|
+
* @returns An array of power of two sizes.
|
|
6
|
+
*/
|
|
7
|
+
export function getPowerOfTwoSizesUntil(limit: number = 4096, from?: number): number[] {
|
|
8
|
+
let size = from ?? 1;
|
|
9
|
+
|
|
10
|
+
const result: number[] = [];
|
|
11
|
+
|
|
12
|
+
while (size <= limit) {
|
|
13
|
+
result.push(size);
|
|
14
|
+
size <<= 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getPowerOfTwoUntil(limit: number): number {
|
|
21
|
+
let size = 1;
|
|
22
|
+
|
|
23
|
+
while (size <= limit) {
|
|
24
|
+
size <<= 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return size >> 1;
|
|
28
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { join } from "node:path/posix";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
|
|
6
|
+
export async function ensureSceneDirectories(scenePath: string) {
|
|
7
|
+
await Promise.all([
|
|
8
|
+
fs.ensureDir(join(scenePath, "nodes")),
|
|
9
|
+
fs.ensureDir(join(scenePath, "meshes")),
|
|
10
|
+
fs.ensureDir(join(scenePath, "lods")),
|
|
11
|
+
fs.ensureDir(join(scenePath, "lights")),
|
|
12
|
+
fs.ensureDir(join(scenePath, "cameras")),
|
|
13
|
+
fs.ensureDir(join(scenePath, "geometries")),
|
|
14
|
+
fs.ensureDir(join(scenePath, "skeletons")),
|
|
15
|
+
fs.ensureDir(join(scenePath, "shadowGenerators")),
|
|
16
|
+
fs.ensureDir(join(scenePath, "sceneLinks")),
|
|
17
|
+
fs.ensureDir(join(scenePath, "gui")),
|
|
18
|
+
fs.ensureDir(join(scenePath, "sounds")),
|
|
19
|
+
fs.ensureDir(join(scenePath, "particleSystems")),
|
|
20
|
+
fs.ensureDir(join(scenePath, "morphTargetManagers")),
|
|
21
|
+
fs.ensureDir(join(scenePath, "morphTargets")),
|
|
22
|
+
fs.ensureDir(join(scenePath, "animationGroups")),
|
|
23
|
+
fs.ensureDir(join(scenePath, "sprite-maps")),
|
|
24
|
+
fs.ensureDir(join(scenePath, "sprite-managers")),
|
|
25
|
+
fs.ensureDir(join(scenePath, "nodeParticleSystemSets")),
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function readSceneDirectories(scenePath: string) {
|
|
30
|
+
const [
|
|
31
|
+
nodesFiles,
|
|
32
|
+
meshesFiles,
|
|
33
|
+
lodsFiles,
|
|
34
|
+
lightsFiles,
|
|
35
|
+
cameraFiles,
|
|
36
|
+
skeletonFiles,
|
|
37
|
+
shadowGeneratorFiles,
|
|
38
|
+
sceneLinkFiles,
|
|
39
|
+
guiFiles,
|
|
40
|
+
soundFiles,
|
|
41
|
+
particleSystemFiles,
|
|
42
|
+
morphTargetManagerFiles,
|
|
43
|
+
morphTargetFiles,
|
|
44
|
+
animationGroupFiles,
|
|
45
|
+
spriteMapFiles,
|
|
46
|
+
spriteManagerFiles,
|
|
47
|
+
geometryFiles,
|
|
48
|
+
nodeParticleSystemSetFiles,
|
|
49
|
+
] = await Promise.all([
|
|
50
|
+
readdir(join(scenePath, "nodes")),
|
|
51
|
+
readdir(join(scenePath, "meshes")),
|
|
52
|
+
readdir(join(scenePath, "lods")),
|
|
53
|
+
readdir(join(scenePath, "lights")),
|
|
54
|
+
readdir(join(scenePath, "cameras")),
|
|
55
|
+
readdir(join(scenePath, "skeletons")),
|
|
56
|
+
readdir(join(scenePath, "shadowGenerators")),
|
|
57
|
+
readdir(join(scenePath, "sceneLinks")),
|
|
58
|
+
readdir(join(scenePath, "gui")),
|
|
59
|
+
readdir(join(scenePath, "sounds")),
|
|
60
|
+
readdir(join(scenePath, "particleSystems")),
|
|
61
|
+
readdir(join(scenePath, "morphTargetManagers")),
|
|
62
|
+
readdir(join(scenePath, "morphTargets")),
|
|
63
|
+
readdir(join(scenePath, "animationGroups")),
|
|
64
|
+
readdir(join(scenePath, "sprite-maps")),
|
|
65
|
+
readdir(join(scenePath, "sprite-managers")),
|
|
66
|
+
readdir(join(scenePath, "geometries")),
|
|
67
|
+
readdir(join(scenePath, "nodeParticleSystemSets")),
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
nodesFiles,
|
|
72
|
+
meshesFiles,
|
|
73
|
+
lodsFiles,
|
|
74
|
+
lightsFiles,
|
|
75
|
+
cameraFiles,
|
|
76
|
+
skeletonFiles,
|
|
77
|
+
shadowGeneratorFiles,
|
|
78
|
+
sceneLinkFiles,
|
|
79
|
+
guiFiles,
|
|
80
|
+
soundFiles,
|
|
81
|
+
particleSystemFiles,
|
|
82
|
+
morphTargetManagerFiles,
|
|
83
|
+
morphTargetFiles,
|
|
84
|
+
animationGroupFiles,
|
|
85
|
+
spriteMapFiles,
|
|
86
|
+
spriteManagerFiles,
|
|
87
|
+
geometryFiles,
|
|
88
|
+
nodeParticleSystemSetFiles,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { join } from "node:path/posix";
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new worker and returns its reference.
|
|
6
|
+
* @param path defines the relative path to the worker entry point relative to THIS file.
|
|
7
|
+
*/
|
|
8
|
+
export function loadWorker(path: string, workerData: WorkerMessageData) {
|
|
9
|
+
path = join(import.meta.dirname.replace(/\\/g, "/"), path);
|
|
10
|
+
return new Worker(path, {
|
|
11
|
+
workerData,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type WorkerMessageData = { id?: string } & Record<string, any>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new worker and returns the result computed by the worker.
|
|
19
|
+
* Posts the given message data to the worker and waits until the worker posts a message back.
|
|
20
|
+
* @param path defines the relative path to the worker according to THIS file. Typically "workers/myWorker.js".
|
|
21
|
+
* @param data defines the data object posted to the worker once it has been initialized.
|
|
22
|
+
* @param transfer defines the optional transferable objects to pass to the worker.
|
|
23
|
+
* @returns a promise that resolves with the data posted by the worker.
|
|
24
|
+
*/
|
|
25
|
+
export async function executeSimpleWorker<T>(pathOrWorker: string, data: WorkerMessageData) {
|
|
26
|
+
const worker = loadWorker(pathOrWorker, data);
|
|
27
|
+
|
|
28
|
+
return new Promise<T>((resolve) => {
|
|
29
|
+
worker.on("message", (result) => {
|
|
30
|
+
if (data.id && result.id !== data.id) {
|
|
31
|
+
return;
|
|
32
|
+
} else if (!data.id) {
|
|
33
|
+
worker.terminate();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
resolve(result);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { workerData, parentPort } from "node:worker_threads";
|
|
2
|
+
|
|
3
|
+
import md5 from "md5";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
|
|
6
|
+
let content = workerData;
|
|
7
|
+
|
|
8
|
+
if (typeof workerData === "string") {
|
|
9
|
+
content = await fs.readFile(workerData);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const hash = md5(content);
|
|
13
|
+
parentPort?.postMessage(hash);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"sourceMap": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"outDir": "./build",
|
|
9
|
+
"declarationDir": "./declaration",
|
|
10
|
+
"strictNullChecks": true,
|
|
11
|
+
"noUnusedLocals": true,
|
|
12
|
+
"noUnusedParameters": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"esModuleInterop": true,
|
|
16
|
+
"downlevelIteration": true,
|
|
17
|
+
"experimentalDecorators": true,
|
|
18
|
+
"skipLibCheck": true
|
|
19
|
+
},
|
|
20
|
+
"exclude": ["node_modules", "build", "declaration", "vitest.config.ts"]
|
|
21
|
+
}
|