@@ -43,6 +43,7 @@
|
|
43
43
|
import { vite_4_4_hack } from "./vite-4.4-hack.js";
|
44
44
|
import { needleImportsLogger } from "./imports-logger.js";
|
45
45
|
import { needleBuildInfo } from "./buildinfo.js";
|
46
|
+
import { needleBuildPipeline } from "./build-pipeline.js";
|
46
47
|
|
47
48
|
|
48
49
|
export * from "./gzip.js";
|
@@ -79,6 +80,7 @@
|
|
79
80
|
vite_4_4_hack(command, config, userSettings),
|
80
81
|
needleFacebookInstantGames(command, config, userSettings),
|
81
82
|
needleImportsLogger(command, config, userSettings),
|
83
|
+
needleBuildPipeline(command, config, userSettings),
|
82
84
|
];
|
83
85
|
array.push(await editorConnection(command, config, userSettings, array));
|
84
86
|
return array;
|
@@ -1,8 +1,10 @@
|
|
1
1
|
import { Color, LinearSRGBColorSpace, Object3D, SRGBColorSpace, Texture } from 'three';
|
2
2
|
import * as ThreeMeshUI from 'three-mesh-ui'
|
3
|
+
import type { Options } from 'three-mesh-ui/build/types/core/elements/MeshUIBaseElement.js';
|
3
4
|
import SimpleStateBehavior from "three-mesh-ui/examples/behaviors/states/SimpleStateBehavior.js"
|
4
5
|
|
5
6
|
import { serializable } from '../../engine/engine_serialization_decorator.js';
|
7
|
+
import { NEEDLE_progressive } from '../../engine/extensions/NEEDLE_progressive.js';
|
6
8
|
import { GameObject } from '../Component.js';
|
7
9
|
import { RGBAColor } from "../js-extensions/RGBAColor.js"
|
8
10
|
import { BaseUIComponent } from "./BaseUIComponent.js";
|
@@ -117,7 +119,7 @@
|
|
117
119
|
}
|
118
120
|
}
|
119
121
|
|
120
|
-
setOptions(opts:
|
122
|
+
setOptions(opts: Options) {
|
121
123
|
this.makePanel();
|
122
124
|
if (this.uiObject) {
|
123
125
|
//@ts-ignore
|
@@ -218,9 +220,14 @@
|
|
218
220
|
}
|
219
221
|
// }
|
220
222
|
this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
|
223
|
+
NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, tex, 0).then(res => {
|
224
|
+
if (res instanceof Texture) {
|
225
|
+
this.setOptions({ backgroundImage: res });
|
226
|
+
}
|
227
|
+
});
|
221
228
|
}
|
222
229
|
else {
|
223
|
-
this.setOptions({ backgroundImage:
|
230
|
+
this.setOptions({ backgroundImage: undefined, borderRadius: 0, backgroundOpacity: this.color.alpha });
|
224
231
|
}
|
225
232
|
this.markDirty();
|
226
233
|
}
|
@@ -40,7 +40,8 @@
|
|
40
40
|
private pixelsPerUnitMultiplier: number = 1;
|
41
41
|
|
42
42
|
private isBuiltinSprite() {
|
43
|
-
|
43
|
+
const sprite = this.sprite;
|
44
|
+
switch (sprite?.texture?.name) {
|
44
45
|
case "InputFieldBackground":
|
45
46
|
case "UISprite":
|
46
47
|
case "Background":
|
@@ -49,7 +50,7 @@
|
|
49
50
|
}
|
50
51
|
// this is a hack/workaround for production builds where the name of the sprite is missing
|
51
52
|
// need to remove this!!!!
|
52
|
-
if (!
|
53
|
+
if (!sprite?.texture?.name?.length && sprite?.texture?.image?.width === 32 && sprite?.texture?.image?.height === 32)
|
53
54
|
return true;
|
54
55
|
return false;
|
55
56
|
}
|
@@ -22,6 +22,7 @@
|
|
22
22
|
if (debug) {
|
23
23
|
window.addEventListener("keyup", evt => {
|
24
24
|
if (evt.key === "p") {
|
25
|
+
console.log("Toggle progressive textures", debug_toggle_maps);
|
25
26
|
debug_toggle_maps.forEach((map, material) => {
|
26
27
|
Object.entries(map).forEach(([key, value]) => {
|
27
28
|
if (show_lod0) {
|
@@ -43,51 +44,68 @@
|
|
43
44
|
return EXTENSION_NAME;
|
44
45
|
}
|
45
46
|
|
46
|
-
|
47
|
-
|
47
|
+
/** Load a different resolution of a texture (if available)
|
48
|
+
* @param context the context
|
49
|
+
* @param source the sourceid of the file from which the texture is loaded (this is usually the component's sourceId)
|
50
|
+
* @param materialOrTexture the material or texture to load the LOD for (if passing in a material all textures in the material will be loaded)
|
51
|
+
* @param level the level of detail to load (0 is the highest resolution) - currently only 0 is supported
|
52
|
+
*/
|
53
|
+
static assignTextureLOD(context: Context, source: SourceIdentifier | undefined, materialOrTexture: Material | Texture, level: number = 0): Promise<any> {
|
54
|
+
if (!materialOrTexture) return Promise.resolve(null);
|
48
55
|
|
49
|
-
|
56
|
+
if (materialOrTexture instanceof Material) {
|
57
|
+
const material = materialOrTexture;
|
58
|
+
const promises: Array<Promise<Texture | null>> = [];
|
50
59
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
60
|
+
if (material instanceof RawShaderMaterial) {
|
61
|
+
// iterate uniforms of custom shaders
|
62
|
+
for (const slot of Object.keys(material.uniforms)) {
|
63
|
+
const val = material.uniforms[slot].value;
|
64
|
+
if (val instanceof Texture) {
|
65
|
+
const task = this.assignTextureLODForSlot(context, source, val, level, material, slot);
|
66
|
+
promises.push(task);
|
67
|
+
}
|
59
68
|
}
|
60
69
|
}
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
70
|
+
else {
|
71
|
+
for (const slot of Object.keys(material)) {
|
72
|
+
const val = material[slot];
|
73
|
+
if (val instanceof Texture) {
|
74
|
+
const task = this.assignTextureLODForSlot(context, source, val, level, material, slot);
|
75
|
+
promises.push(task);
|
76
|
+
}
|
68
77
|
}
|
69
78
|
}
|
79
|
+
return PromiseAllWithErrors(promises);
|
70
80
|
}
|
71
81
|
|
72
|
-
|
82
|
+
if (materialOrTexture instanceof Texture) {
|
83
|
+
const texture = materialOrTexture;
|
84
|
+
return this.assignTextureLODForSlot(context, source, texture, level, null, null);
|
85
|
+
}
|
86
|
+
|
87
|
+
return Promise.resolve(null);
|
73
88
|
}
|
74
89
|
|
75
|
-
private static assignTextureLODForSlot(context: Context, source: SourceIdentifier | undefined,
|
90
|
+
private static assignTextureLODForSlot(context: Context, source: SourceIdentifier | undefined, val: any, level: number, material: Material | null, slot: string | null): Promise<Texture | null> {
|
76
91
|
if (val?.isTexture !== true) return Promise.resolve(null);
|
77
92
|
|
78
|
-
if (debug) console.log("-----------\n", "FIND", material
|
93
|
+
if (debug) console.log("-----------\n", "FIND", material?.name, slot, val?.name, val?.userData, val, material);
|
79
94
|
|
80
|
-
return NEEDLE_progressive.getOrLoadTexture(context, source,
|
95
|
+
return NEEDLE_progressive.getOrLoadTexture(context, source, val, level, material, slot).then(t => {
|
81
96
|
|
82
97
|
if (t?.isTexture === true) {
|
83
98
|
|
84
|
-
if (debug) console.warn("Assign LOD", material
|
99
|
+
if (debug) console.warn("Assign LOD", material?.name, slot, t.name, t["guid"], material, "Prev:", val, "Now:", t, "\n--------------");
|
85
100
|
|
86
|
-
material[slot] = t;
|
87
101
|
t.needsUpdate = true;
|
88
|
-
material.needsUpdate = true;
|
89
102
|
|
90
|
-
if (
|
103
|
+
if (material && slot) {
|
104
|
+
material[slot] = t;
|
105
|
+
material.needsUpdate = true;
|
106
|
+
}
|
107
|
+
|
108
|
+
if (debug && material && slot) {
|
91
109
|
let debug_map = debug_toggle_maps.get(material);
|
92
110
|
if (!debug_map) {
|
93
111
|
debug_map = {};
|
@@ -146,16 +164,16 @@
|
|
146
164
|
private static resolved: { [key: string]: Texture } = {};
|
147
165
|
private static currentlyLoading: { [key: string]: Promise<Texture | null> } = {};
|
148
166
|
|
149
|
-
private static async getOrLoadTexture(context: Context, source: SourceIdentifier | undefined,
|
167
|
+
private static async getOrLoadTexture(context: Context, source: SourceIdentifier | undefined, current: Texture, _level: number, material: Material | null, slot: string | null): Promise<Texture | null> {
|
150
168
|
|
151
169
|
const key = current.uuid;
|
152
170
|
|
153
171
|
let progressiveInfo: ProgressiveTextureSchema | undefined;
|
154
172
|
|
155
173
|
// See https://github.com/needle-tools/needle-engine-support/issues/133
|
156
|
-
if(current.source && current.source[$progressiveTextureExtension])
|
174
|
+
if (current.source && current.source[$progressiveTextureExtension])
|
157
175
|
progressiveInfo = current.source[$progressiveTextureExtension];
|
158
|
-
if(!progressiveInfo) progressiveInfo = NEEDLE_progressive.cache.get(key);
|
176
|
+
if (!progressiveInfo) progressiveInfo = NEEDLE_progressive.cache.get(key);
|
159
177
|
|
160
178
|
if (progressiveInfo) {
|
161
179
|
if (debug)
|
@@ -171,12 +189,12 @@
|
|
171
189
|
let res = this.resolved[resolveKey];
|
172
190
|
// check if the texture has been disposed or not
|
173
191
|
if (res.image?.data || res.source?.data) {
|
174
|
-
if (debug) console.log("Texture has already been loaded: " + resolveKey, material
|
192
|
+
if (debug) console.log("Texture has already been loaded: " + resolveKey, material?.name, slot, current.name, res);
|
175
193
|
res = this.copySettings(current, res);
|
176
194
|
return res;
|
177
195
|
}
|
178
196
|
else if (res) {
|
179
|
-
if(debug) console.warn("Texture has been disposed, will load again: " + resolveKey, material
|
197
|
+
if (debug) console.warn("Texture has been disposed, will load again: " + resolveKey, material?.name, slot, current.name, res.source.data);
|
180
198
|
}
|
181
199
|
}
|
182
200
|
|
@@ -184,7 +202,7 @@
|
|
184
202
|
try {
|
185
203
|
if (this.currentlyLoading[resolveKey] !== undefined) {
|
186
204
|
if (debug)
|
187
|
-
console.log("Already loading:", material
|
205
|
+
console.log("Already loading:", material?.name + "." + slot, resolveKey);
|
188
206
|
let res = await this.currentlyLoading[resolveKey];
|
189
207
|
if (res)
|
190
208
|
res = this.copySettings(current, res);
|
@@ -196,19 +214,19 @@
|
|
196
214
|
addDracoAndKTX2Loaders(loader, context);
|
197
215
|
|
198
216
|
|
199
|
-
if (debug) console.warn("Start loading " + uri, material
|
217
|
+
if (debug) console.warn("Start loading " + uri, material?.name, slot, ext.guid);
|
200
218
|
if (debug) {
|
201
219
|
await delay(Math.random() * 1000);
|
202
220
|
}
|
203
221
|
|
204
222
|
const gltf = await loader.loadAsync(uri);
|
205
223
|
const parser = gltf.parser;
|
206
|
-
if (debug) console.log("Loading finished " + uri, material
|
224
|
+
if (debug) console.log("Loading finished " + uri, material?.name, slot, ext.guid);
|
207
225
|
let index = -1;
|
208
226
|
let found = false;
|
209
|
-
|
227
|
+
|
210
228
|
if (!gltf.parser.json?.textures) {
|
211
|
-
if (debug) console.warn("No textures in glTF " + uri + " - may be a bug", material
|
229
|
+
if (debug) console.warn("No textures in glTF " + uri + " - may be a bug", material?.name, slot, ext.guid);
|
212
230
|
return resolve(null);
|
213
231
|
}
|
214
232
|
|
@@ -234,7 +252,7 @@
|
|
234
252
|
}
|
235
253
|
this.resolved[resolveKey] = tex as Texture;
|
236
254
|
if (debug)
|
237
|
-
console.log(material
|
255
|
+
console.log(material?.name, slot, "change \"" + current.name + "\" → \"" + tex.name + "\"", uri, index, tex, material, resolveKey);
|
238
256
|
resolve(tex);
|
239
257
|
});
|
240
258
|
this.currentlyLoading[resolveKey] = request;
|
@@ -343,7 +361,7 @@
|
|
343
361
|
private static _currentProgressiveLoadingInfo: Map<Context, ProgressiveLoadingInfo[]> = new Map();
|
344
362
|
|
345
363
|
// called whenever a progressive loading event starts
|
346
|
-
private static onProgressiveLoadStart(context: Context, source: SourceIdentifier | undefined, uri: string, material: Material, slot: string): ProgressiveLoadingInfo {
|
364
|
+
private static onProgressiveLoadStart(context: Context, source: SourceIdentifier | undefined, uri: string, material: Material | null, slot: string | null): ProgressiveLoadingInfo {
|
347
365
|
if (!this._currentProgressiveLoadingInfo.has(context)) {
|
348
366
|
this._currentProgressiveLoadingInfo.set(context, []);
|
349
367
|
}
|
@@ -379,11 +397,11 @@
|
|
379
397
|
readonly context: Context;
|
380
398
|
readonly source: SourceIdentifier | undefined;
|
381
399
|
readonly uri: string;
|
382
|
-
readonly material?: Material;
|
383
|
-
readonly slot?: string;
|
400
|
+
readonly material?: Material | null;
|
401
|
+
readonly slot?: string | null;
|
384
402
|
// TODO: can contain information if the event is a background process / preloading or if the object is currently visible
|
385
403
|
|
386
|
-
constructor(context: Context, source: SourceIdentifier | undefined, uri: string, material?: Material, slot?: string) {
|
404
|
+
constructor(context: Context, source: SourceIdentifier | undefined, uri: string, material?: Material | null, slot?: string | null) {
|
387
405
|
this.context = context;
|
388
406
|
this.source = source;
|
389
407
|
this.uri = uri;
|
@@ -9,6 +9,7 @@
|
|
9
9
|
allowHotReload: boolean,
|
10
10
|
license: string | null,
|
11
11
|
useRapier: boolean,
|
12
|
+
developmentBuild: boolean,
|
12
13
|
}
|
13
14
|
|
14
15
|
|
@@ -276,7 +276,7 @@
|
|
276
276
|
}
|
277
277
|
else if (debug) console.warn("PlayerState.start → owner is still undefined but dontDestroy is set to true", this.name);
|
278
278
|
}
|
279
|
-
else console.log("PlayerState.start → owner is assigned", this.owner);
|
279
|
+
else if(debug) console.log("PlayerState.start → owner is assigned", this.owner);
|
280
280
|
}, 2000);
|
281
281
|
}
|
282
282
|
}
|
@@ -1,8 +1,10 @@
|
|
1
1
|
import * as THREE from "three";
|
2
2
|
import { Material, NearestFilter, Texture } from "three";
|
3
3
|
|
4
|
+
import { Context } from "../engine/engine_context.js";
|
4
5
|
import { serializable, serializeable } from "../engine/engine_serialization_decorator.js";
|
5
6
|
import { getParam } from "../engine/engine_utils.js";
|
7
|
+
import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive.js";
|
6
8
|
import { Behaviour } from "./Component.js";
|
7
9
|
import { RGBAColor } from "./js-extensions/RGBAColor.js";
|
8
10
|
|
@@ -70,7 +72,6 @@
|
|
70
72
|
}
|
71
73
|
|
72
74
|
export class Sprite {
|
73
|
-
|
74
75
|
@serializable()
|
75
76
|
guid?: string;
|
76
77
|
@serializable(Texture)
|
@@ -83,6 +84,7 @@
|
|
83
84
|
vertices!: Array<Vec2>;
|
84
85
|
|
85
86
|
_geometry?: THREE.BufferGeometry;
|
87
|
+
_hasLoadedProgressive: boolean = false;
|
86
88
|
}
|
87
89
|
|
88
90
|
const $spriteTexOwner = Symbol("spriteOwner");
|
@@ -101,7 +103,7 @@
|
|
101
103
|
@serializable()
|
102
104
|
index: number = 0;
|
103
105
|
|
104
|
-
update() {
|
106
|
+
update(context: Context, sourceId: string | undefined, material: Material | undefined) {
|
105
107
|
if (!this.spriteSheet) return;
|
106
108
|
const index = this.index;
|
107
109
|
if (index < 0 || index >= this.spriteSheet.sprites.length)
|
@@ -113,6 +115,21 @@
|
|
113
115
|
if (tex.minFilter == NearestFilter && tex.magFilter == NearestFilter)
|
114
116
|
tex.anisotropy = 1;
|
115
117
|
tex.needsUpdate = true;
|
118
|
+
|
119
|
+
if (!slice._hasLoadedProgressive) {
|
120
|
+
slice._hasLoadedProgressive = true;
|
121
|
+
const previousTexture = tex;
|
122
|
+
NEEDLE_progressive.assignTextureLOD(context, sourceId, tex, 0).then(res => {
|
123
|
+
if (res instanceof Texture) {
|
124
|
+
slice.texture = res;
|
125
|
+
const shouldUpdateInMaterial = material?.["map"] === previousTexture;
|
126
|
+
if (shouldUpdateInMaterial) {
|
127
|
+
material["map"] = res;
|
128
|
+
material.needsUpdate = true;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
});
|
132
|
+
}
|
116
133
|
}
|
117
134
|
}
|
118
135
|
|
@@ -137,8 +154,11 @@
|
|
137
154
|
if (value === this._spriteSheet) return;
|
138
155
|
if (typeof value === "number") {
|
139
156
|
const index = Math.floor(value);
|
140
|
-
if (
|
141
|
-
|
157
|
+
// if (value === index)
|
158
|
+
this.spriteIndex = index;
|
159
|
+
// else if (debug) {
|
160
|
+
// console.log("Spritesheet framedrop", index, value);
|
161
|
+
// }
|
142
162
|
return;
|
143
163
|
}
|
144
164
|
else {
|
@@ -172,7 +192,7 @@
|
|
172
192
|
|
173
193
|
awake(): void {
|
174
194
|
this._currentSprite = undefined;
|
175
|
-
if(debug) {
|
195
|
+
if (debug) {
|
176
196
|
console.log("Awake", this.name, this, this.sprite);
|
177
197
|
}
|
178
198
|
}
|
@@ -218,6 +238,7 @@
|
|
218
238
|
}
|
219
239
|
this.sharedMaterial = mat;
|
220
240
|
this._currentSprite = new THREE.Mesh(SpriteUtils.getOrCreateGeometry(sprite), mat);
|
241
|
+
NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, mat, 0);
|
221
242
|
}
|
222
243
|
else {
|
223
244
|
this._currentSprite.geometry = SpriteUtils.getOrCreateGeometry(sprite);
|
@@ -231,7 +252,7 @@
|
|
231
252
|
this.gameObject.add(this._currentSprite);
|
232
253
|
}
|
233
254
|
|
234
|
-
if(this._currentSprite){
|
255
|
+
if (this._currentSprite) {
|
235
256
|
this._currentSprite.layers.set(this.layer)
|
236
257
|
}
|
237
258
|
|
@@ -240,6 +261,6 @@
|
|
240
261
|
this.sharedMaterial.transparent = this.transparent;
|
241
262
|
}
|
242
263
|
this._currentSprite.castShadow = this.castShadows;
|
243
|
-
this._spriteSheet?.update();
|
264
|
+
this._spriteSheet?.update(this.context, this.sourceId, this.sharedMaterial);
|
244
265
|
}
|
245
266
|
}
|
@@ -28,6 +28,9 @@
|
|
28
28
|
/** Set to true to disable generating the buildinfo.json file in your output directory */
|
29
29
|
noBuildInfo: boolean;
|
30
30
|
|
31
|
+
/** Set to true to disable the needle build pipeline (running compression and optimization as a postprocessing step on the exported glTF files) */
|
32
|
+
noBuildPipeline: boolean;
|
33
|
+
|
31
34
|
/** required for @serializable https://github.com/vitejs/vite/issues/13736 */
|
32
35
|
vite44Hack: boolean;
|
33
36
|
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import { exec, spawn } from 'child_process';
|
2
|
+
import { getOutputDirectory, loadConfig, tryLoadProjectConfig } from './config.js';
|
3
|
+
import { existsSync, readdirSync } from 'fs';
|
4
|
+
|
5
|
+
// see https://linear.app/needle/issue/NE-3798
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Runs the needle build pipeline as part of the vite build process
|
9
|
+
* @param {import('../types').userSettings} userSettings
|
10
|
+
* @returns {import('vite').Plugin}
|
11
|
+
*/
|
12
|
+
export const needleBuildPipeline = async (command, config, userSettings) => {
|
13
|
+
// we only want to run compression here if this is a distribution build
|
14
|
+
if (command !== "build") return;
|
15
|
+
if (userSettings.noBuildPipeline) return;
|
16
|
+
|
17
|
+
const meta = await loadConfig();
|
18
|
+
let shouldRun = false;
|
19
|
+
if (meta && meta.developmentBuild === false) {
|
20
|
+
shouldRun = true;
|
21
|
+
}
|
22
|
+
if (!shouldRun) {
|
23
|
+
log("Skipping build pipeline because this is a development build");
|
24
|
+
return;
|
25
|
+
}
|
26
|
+
|
27
|
+
/** @type {Promise<any>|null} */
|
28
|
+
let task = null;
|
29
|
+
let taskFinished = false;
|
30
|
+
let taskSucceeded = false;
|
31
|
+
return {
|
32
|
+
name: 'needle-buildpipeline',
|
33
|
+
enforce: "post",
|
34
|
+
apply: 'build',
|
35
|
+
buildEnd() {
|
36
|
+
// start the compression process once vite is done copying the files
|
37
|
+
task = invokeBuildPipeline().then((res) => {
|
38
|
+
taskFinished = true;
|
39
|
+
taskSucceeded = res;
|
40
|
+
});
|
41
|
+
},
|
42
|
+
closeBundle() {
|
43
|
+
// this is the last hook that is called, so we can wait for the task to finish here
|
44
|
+
if (taskFinished) return;
|
45
|
+
// delay the final log slightly to give other plugins a chance to log their stuff
|
46
|
+
wait(100).then(() => {
|
47
|
+
if (!taskFinished) log("Waiting for postprocessing to finish...")
|
48
|
+
});
|
49
|
+
return task.then(_ => {
|
50
|
+
log("finished", taskSucceeded ? "successfully" : "with errors");
|
51
|
+
});
|
52
|
+
},
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
function log(...args) {
|
57
|
+
console.log("[needle-buildpipeline]", ...args);
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* @param {*} opts
|
62
|
+
* @returns {Promise<boolean>}
|
63
|
+
*/
|
64
|
+
async function invokeBuildPipeline(opts) {
|
65
|
+
await wait(1000);
|
66
|
+
const outputDirectory = getOutputDirectory() + "/assets";
|
67
|
+
if (!existsSync(outputDirectory)) {
|
68
|
+
log("No output directory found at ", outputDirectory);
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf"));
|
72
|
+
log(files.length + " file(s) to process");
|
73
|
+
|
74
|
+
const cmd = "npm run transform --prefix node_modules/@needle-tools/gltf-build-pipeline";
|
75
|
+
log("Running command \"" + cmd + "\" at " + process.cwd() + "...");
|
76
|
+
const sub = exec(cmd);
|
77
|
+
sub.stdout.on('data', data => {
|
78
|
+
if (data.length <= 0) return;
|
79
|
+
// ensure that it doesnt end with a newline
|
80
|
+
if (data.endsWith("\n")) data = data.slice(0, -1);
|
81
|
+
console.log(data);
|
82
|
+
});
|
83
|
+
sub.stderr.on('data', console.error);
|
84
|
+
return new Promise((resolve, reject) => {
|
85
|
+
sub.on('exit', (code) => {
|
86
|
+
resolve(code === 0);
|
87
|
+
});
|
88
|
+
});
|
89
|
+
}
|
90
|
+
|
91
|
+
function wait(ms) {
|
92
|
+
return new Promise((resolve, reject) => {
|
93
|
+
setTimeout(() => {
|
94
|
+
resolve();
|
95
|
+
}, ms);
|
96
|
+
});
|
97
|
+
}
|