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.
Files changed (82) hide show
  1. package/bin/babylonjs-editor-cli.js +3 -0
  2. package/build/index.node.js +21 -0
  3. package/build/package.json +34 -0
  4. package/build/src/export.mjs +2 -0
  5. package/build/src/export.mjs.map +1 -0
  6. package/build/src/index.mjs +22 -0
  7. package/build/src/index.mjs.map +1 -0
  8. package/build/src/pack/assets/assets.mjs +26 -0
  9. package/build/src/pack/assets/assets.mjs.map +1 -0
  10. package/build/src/pack/assets/ktx.mjs +121 -0
  11. package/build/src/pack/assets/ktx.mjs.map +1 -0
  12. package/build/src/pack/assets/material.mjs +53 -0
  13. package/build/src/pack/assets/material.mjs.map +1 -0
  14. package/build/src/pack/assets/particle-system.mjs +75 -0
  15. package/build/src/pack/assets/particle-system.mjs.map +1 -0
  16. package/build/src/pack/assets/process.mjs +78 -0
  17. package/build/src/pack/assets/process.mjs.map +1 -0
  18. package/build/src/pack/assets/texture.mjs +61 -0
  19. package/build/src/pack/assets/texture.mjs.map +1 -0
  20. package/build/src/pack/geometry.mjs +19 -0
  21. package/build/src/pack/geometry.mjs.map +1 -0
  22. package/build/src/pack/pack.mjs +127 -0
  23. package/build/src/pack/pack.mjs.map +1 -0
  24. package/build/src/pack/scene.mjs +241 -0
  25. package/build/src/pack/scene.mjs.map +1 -0
  26. package/build/src/tools/extract.mjs +51 -0
  27. package/build/src/tools/extract.mjs.map +1 -0
  28. package/build/src/tools/fs.mjs +9 -0
  29. package/build/src/tools/fs.mjs.map +1 -0
  30. package/build/src/tools/ktx.mjs +36 -0
  31. package/build/src/tools/ktx.mjs.map +1 -0
  32. package/build/src/tools/process.mjs +21 -0
  33. package/build/src/tools/process.mjs.map +1 -0
  34. package/build/src/tools/scalar.mjs +23 -0
  35. package/build/src/tools/scalar.mjs.map +1 -0
  36. package/build/src/tools/scene.mjs +68 -0
  37. package/build/src/tools/scene.mjs.map +1 -0
  38. package/build/src/tools/worker.mjs +35 -0
  39. package/build/src/tools/worker.mjs.map +1 -0
  40. package/build/src/tools/workers/md5.mjs +10 -0
  41. package/build/src/tools/workers/md5.mjs.map +1 -0
  42. package/declaration/src/export.d.mts +1 -0
  43. package/declaration/src/index.d.mts +1 -0
  44. package/declaration/src/pack/assets/assets.d.mts +11 -0
  45. package/declaration/src/pack/assets/ktx.d.mts +28 -0
  46. package/declaration/src/pack/assets/material.d.mts +11 -0
  47. package/declaration/src/pack/assets/particle-system.d.mts +12 -0
  48. package/declaration/src/pack/assets/process.d.mts +9 -0
  49. package/declaration/src/pack/assets/texture.d.mts +7 -0
  50. package/declaration/src/pack/geometry.d.mts +10 -0
  51. package/declaration/src/pack/pack.d.mts +5 -0
  52. package/declaration/src/pack/scene.d.mts +13 -0
  53. package/declaration/src/tools/extract.d.mts +8 -0
  54. package/declaration/src/tools/fs.d.mts +2 -0
  55. package/declaration/src/tools/ktx.d.mts +3 -0
  56. package/declaration/src/tools/process.d.mts +5 -0
  57. package/declaration/src/tools/scalar.d.mts +8 -0
  58. package/declaration/src/tools/scene.d.mts +21 -0
  59. package/declaration/src/tools/worker.d.mts +18 -0
  60. package/declaration/src/tools/workers/md5.d.mts +1 -0
  61. package/esbuild.mjs +57 -0
  62. package/package.json +34 -0
  63. package/src/export.mts +1 -0
  64. package/src/index.mts +28 -0
  65. package/src/pack/assets/assets.mts +46 -0
  66. package/src/pack/assets/ktx.mts +158 -0
  67. package/src/pack/assets/material.mts +86 -0
  68. package/src/pack/assets/particle-system.mts +109 -0
  69. package/src/pack/assets/process.mts +102 -0
  70. package/src/pack/assets/texture.mts +91 -0
  71. package/src/pack/geometry.mts +38 -0
  72. package/src/pack/pack.mts +159 -0
  73. package/src/pack/scene.mts +346 -0
  74. package/src/tools/extract.mts +74 -0
  75. package/src/tools/fs.mts +11 -0
  76. package/src/tools/ktx.mts +44 -0
  77. package/src/tools/process.mts +21 -0
  78. package/src/tools/scalar.mts +28 -0
  79. package/src/tools/scene.mts +90 -0
  80. package/src/tools/worker.mts +39 -0
  81. package/src/tools/workers/md5.mts +13 -0
  82. 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
+ }
@@ -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
+ }