Needle Engine

Changes between version 3.48.0-experimental.10 and 3.47.4-beta
Files changed (22) hide show
  1. plugins/vite/alias.js +8 -8
  2. plugins/vite/build-pipeline.js +69 -248
  3. plugins/common/buildinfo.js +3 -4
  4. plugins/vite/buildinfo.js +14 -20
  5. plugins/vite/copyfiles.js +67 -68
  6. plugins/vite/dependencies.js +16 -78
  7. plugins/common/files.js +0 -32
  8. plugins/vite/index.js +1 -1
  9. plugins/common/timers.js +0 -8
  10. src/engine/js-extensions/Camera.ts +6 -3
  11. src/engine/engine_element.ts +7 -0
  12. src/engine/engine_physics.ts +27 -21
  13. src/engine/engine_utils_screenshot.ts +1 -1
  14. src/engine-components/ui/EventSystem.ts +3 -0
  15. src/engine/analytics/index.ts +0 -11
  16. src/engine/analytics/lcp.ts +0 -35
  17. src/asap/needle-asap.ts +13 -25
  18. src/engine/webcomponents/needle menu/needle-menu.ts +1 -1
  19. plugins/types/needleConfig.d.ts +0 -3
  20. src/engine/codegen/register_types.ts +2 -2
  21. plugins/types/userconfig.d.ts +5 -23
  22. src/engine/engine.ts +14 -0
plugins/vite/alias.js CHANGED
@@ -15,14 +15,14 @@
15
15
  return res + "/../lib";
16
16
  }
17
17
  },
18
- /*
19
- Removed the previously present @needle-tools/engine entry
20
- because this is automatically done by vite according to whatever we define in our package.json exports
21
- This did previously prevent us from declaring proper exports in package.json
22
- */
23
-
24
- /*
25
- Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
18
+ '@needle-tools/engine': (res, packageName, index, path) => {
19
+ // Check if the import is something like @needle-tools/engine/engine/engine_utils
20
+ // in which case we want to resolve into the lib directory
21
+ if (path.startsWith("@needle-tools/engine/engine")) {
22
+ return res + "/lib";
23
+ }
24
+ },
25
+ /* Removed. Three.js is manually resolved below to ensure all dependencies resolve to the same three.js version.
26
26
  'three': null,
27
27
  */
28
28
  'peerjs': null,
plugins/vite/build-pipeline.js CHANGED
@@ -1,85 +1,21 @@
1
- import { ChildProcess, exec } from 'child_process';
2
- import { getOutputDirectory, loadConfig } from './config.js';
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
4
- import { copyFilesSync } from '../common/files.js';
5
- import { delay } from '../common/timers.js';
1
+ import { ChildProcess, exec, execSync, spawn } from 'child_process';
2
+ import { getOutputDirectory, loadConfig, tryLoadProjectConfig } from './config.js';
3
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs';
6
4
 
7
- /**
8
- * @param {import('../types').userSettings} config
9
- * @returns {boolean}
10
- */
11
- function validateCloudBuildConfiguration(config) {
12
- if (config.buildPipeline != undefined) {
13
- if (process.env.CI) {
14
- if ((process.env.NEEDLE_CLOUD_TOKEN == undefined || process.env.NEEDLE_CLOUD_TOKEN.length <= 0)) {
15
- const isGithubAction = process.env.CI && process.env.GITHUB_ACTION;
16
- let msg = `Missing Needle Cloud access token. Please set your Needle Cloud token via \`process.env.NEEDLE_CLOUD_TOKEN\`.`;
17
- if (isGithubAction) {
18
- msg += `
19
- Make sure to pass the token as a secret to the Github action.
20
- For this you may have to modify your .github workflow yaml file to include the following code:
21
- env:
22
- - NEEDLE_CLOUD_TOKEN: \${{ secrets.NEEDLE_CLOUD_TOKEN }}
23
- `;
24
- let error = `${msg}`;
25
- throw new Error(error);
26
- }
27
- }
28
- }
29
- }
30
-
31
- return true;
32
- }
33
-
34
5
  // see https://linear.app/needle/issue/NE-3798
35
6
 
7
+ export let buildPipelineTask;
36
8
 
37
- /** @type {Promise<any>|null} */
38
- let buildPipelineTask;
39
- /** @type {null | {tempDirectory:string, outputDirectory:string}} */
40
- let buildPipelineTaskResults = null;
41
-
42
- export function waitForBuildPipelineToFinish() {
43
- return buildPipelineTask;
44
- }
45
-
46
- /** This time is set when a build plugin is triggered to run
47
- * We increase the time by 10-20 seconds each time because we might have a multi step process
48
- * where the build pipeline is triggered in the SSR build (which we can not always detect)
49
- * and the final client build is triggered later (but the build pipeline is still running and waiting)
50
- */
51
- let maxOutputDirectoryCreatedWaitTime = 0;
52
9
  /**
53
- * @param {boolean} debugLog
54
- */
55
- function increaseMaxWaitTime(debugLog) {
56
- maxOutputDirectoryCreatedWaitTime = Date.now();
57
- let maxWaitTime = 10_000;
58
- if (process.env.CI) {
59
- maxWaitTime += 50_000;
60
- }
61
- maxOutputDirectoryCreatedWaitTime += maxWaitTime;
62
- if (debugLog) {
63
- log(`Increased max wait time by ${maxWaitTime / 1000}sec until ${new Date(maxOutputDirectoryCreatedWaitTime).toISOString()}`);
64
- }
65
- }
66
-
67
- /**
68
10
  * Runs the needle build pipeline as part of the vite build process
69
- * @param {string} command
70
- * @param {import('vite').UserConfig} config
71
11
  * @param {import('../types').userSettings} userSettings
72
- * @returns {Promise<import('vite').Plugin | null>}
12
+ * @returns {import('vite').Plugin}
73
13
  */
74
14
  export const needleBuildPipeline = async (command, config, userSettings) => {
75
15
 
76
16
  // we only want to run compression here if this is a distribution build
77
17
  // this is handled however in the `apply` hook
78
- if (userSettings.noBuildPipeline) return null;
79
- if (userSettings.buildPipeline?.enabled === false) {
80
- log("Skipping build pipeline because it is disabled in the user settings via `buildPipeline.enabled: false`");
81
- return null;
82
- }
18
+ if (userSettings.noBuildPipeline) return;
83
19
 
84
20
  const packageJsonPath = process.cwd() + "/package.json";
85
21
  await fixPackageJson(packageJsonPath);
@@ -90,102 +26,53 @@
90
26
  if (productionArgument >= 0) {
91
27
  shouldRun = true;
92
28
  }
29
+ else {
30
+ const meta = await loadConfig();
31
+ if (meta && meta.developmentBuild === false) {
32
+ shouldRun = true;
33
+ }
34
+ }
93
35
 
94
36
  if (!shouldRun) {
95
37
  log("Skipping build pipeline because this is a development build.\n- Invoke with `--production` to run the build pipeline.\n- For example \"vite build -- --production\".");
96
38
  await new Promise((resolve, _) => setTimeout(resolve, 1000));
97
- return null;
39
+ return;
98
40
  }
99
41
 
100
- if (process.env.CI) {
101
- log("Running in CI environment");
102
- }
103
-
104
- validateCloudBuildConfiguration(userSettings);
105
-
106
- const verboseOutput = userSettings?.buildPipeline?.verbose || false;
107
- let taskHasCompleted = false;
108
-
42
+ /** @type {Promise<any>|null} */
43
+ let task = null;
44
+ let taskFinished = false;
45
+ let taskSucceeded = false;
109
46
  return {
110
47
  name: 'needle-buildpipeline',
111
48
  enforce: "post",
112
- apply: (_conf, env) => {
113
- if (verboseOutput) {
114
- log("Apply:", env);
115
- }
116
- // Don't run for SSR builds (e.g. sveltekit).
117
- // Unfortunately this is always falls in vite 4.3 so we can not rely on it solely
118
- if (env.ssrBuild) return false;
119
- // Dont run if there's already a build pipeline task running
120
- if (env.command === "build") {
121
- increaseMaxWaitTime(verboseOutput);
122
- if (buildPipelineTask) {
123
- return false;
124
- }
125
- return true;
126
- }
127
- return false;
49
+ apply: 'build',
50
+ buildEnd() {
51
+ // start the compression process once vite is done copying the files
52
+ task = invokeBuildPipeline(userSettings).then((res) => {
53
+ taskFinished = true;
54
+ taskSucceeded = res;
55
+ });
56
+ buildPipelineTask = task;
128
57
  },
129
- buildEnd(error) {
130
- increaseMaxWaitTime(verboseOutput);
131
-
132
- if (verboseOutput) {
133
- log("Build end:", error ?? "No error");
134
- }
135
- if (error) {
136
- // if there was an error during the build we should not run the build pipeline
137
- }
138
- else {
139
- if (buildPipelineTask) {
140
- log("Build pipeline already running...");
141
- return;
142
- }
143
- let taskSucceeded = false;
144
- // start the compression process once vite is done copying the files
145
- buildPipelineTask = invokeBuildPipeline(userSettings)
146
- .then((res) => {
147
- taskSucceeded = res;
148
- })
149
- .finally(() => {
150
- taskHasCompleted = true;
151
- if (!taskSucceeded) {
152
- throw new Error("[needle-buildpipeline] - Build pipeline failed. Check the logs for more information.");
153
- }
154
- else {
155
- log("Finished successfully");
156
- }
157
- });
158
- }
159
- },
160
58
  closeBundle() {
161
- if (!buildPipelineTask) {
162
- return;
163
- }
164
- if (!taskHasCompleted) {
165
- log("Waiting for build pipeline to finish...");
166
- }
167
- // // this is the last hook that is called, so we can wait for the task to finish here
168
- return buildPipelineTask = buildPipelineTask?.then(() => {
169
- // Copy the results to their final output directory.
170
- if (buildPipelineTaskResults != null) {
171
- log("Copying files from temporary output directory to final output directory...");
172
- const ctx = { count: 0 }
173
- copyFilesSync(buildPipelineTaskResults.tempDirectory, buildPipelineTaskResults.outputDirectory, true, ctx);
174
- log(`Copied ${ctx.count} file(s)`);
175
- }
176
- else {
177
- log("No files to copy - build pipeline did not run or did not finish successfully");
178
- }
59
+ log("Waiting for postprocessing to finish...");
60
+ // this is the last hook that is called, so we can wait for the task to finish here
61
+ if (taskFinished) return;
62
+ // delay the final log slightly to give other plugins a chance to log their stuff
63
+ wait(100).then(() => {
64
+ if (!taskFinished) log("Waiting for postprocessing to finish...")
179
65
  });
66
+ return task.then(_ => {
67
+ log("finished", taskSucceeded ? "successfully" : "with errors");
68
+ });
180
69
  },
181
70
  }
182
71
  }
183
72
 
184
-
185
73
  /**
186
74
  * Previously we did always install the build pipeline and run an extra command to invoke the build pipeline.
187
75
  * This is now done automatically by the needle build pipeline plugin - so we update all legacy projects to use the new method.
188
- * @param {string} packageJsonPath
189
76
  */
190
77
  async function fixPackageJson(packageJsonPath) {
191
78
  if (!existsSync(packageJsonPath)) {
@@ -202,11 +89,9 @@
202
89
  writeFileSync(packageJsonPath, fixed);
203
90
  }
204
91
 
205
- /** @param {any} args */
206
92
  function log(...args) {
207
93
  console.log("[needle-buildpipeline]", ...args);
208
94
  }
209
- /** @param {any} args */
210
95
  function warn(...args) {
211
96
  console.warn("WARN: [needle-buildpipeline]", ...args);
212
97
  }
@@ -219,127 +104,63 @@
219
104
  const installPath = "node_modules/@needle-tools/gltf-build-pipeline";
220
105
  const fullInstallPath = process.cwd() + "/" + installPath;
221
106
  const existsLocally = existsSync(fullInstallPath);
222
- if (existsLocally) {
223
- log("Found local build pipeline installation at " + fullInstallPath);
107
+ if (!existsLocally) {
108
+ // await execSync("npx --yes @needle-tools/n")
109
+ // throw new Error(`ERR: Build pipeline not found at \"${fullInstallPath}\". \nTo disable this plugin you can pass \"noBuildPipeline: true\" to the needle config in vite.config.js`);
110
+ warn("@needle-tools/gltf-build-pipeline not found in package - using latest version")
224
111
  }
225
- await delay(500);
112
+ await wait(500);
226
113
  const outputDirectory = getOutputDirectory() + "/assets";
227
- const startWaitTime = Date.now();
228
- const maxEndTime = startWaitTime + 120_000;
229
- /** wait until the output directory exists
230
- * @param {number} iteration
231
- * @returns {Promise<boolean>}
232
- */
114
+ // wait until the output directory exists - this depends on speed
233
115
  function waitForOutputDirectory(iteration) {
234
- // we wait for the output directory
235
116
  if (!existsSync(outputDirectory)) {
236
- if (maxOutputDirectoryCreatedWaitTime != 0 && Date.now() > maxOutputDirectoryCreatedWaitTime) {
117
+ if (iteration > 10) {
237
118
  return Promise.resolve(false);
238
119
  }
239
- else if (Date.now() > maxEndTime) {
240
- log("Max wait time exceeded - aborting...");
241
- return Promise.resolve(false);
242
- }
243
- if (iteration <= 0) log(`Waiting for output directory to be created... (${outputDirectory})`);
244
- return delay(1000).then(() => waitForOutputDirectory(iteration + 1));
120
+ if (iteration <= 0) log("Waiting for output directory to be created...");
121
+ return wait(1000).then(() => waitForOutputDirectory(iteration + 1));
245
122
  }
246
123
  return Promise.resolve(true);
247
124
  }
248
125
  if (!await waitForOutputDirectory(0)) {
249
- warn(`Output directory not found/created at \"${outputDirectory}\" - aborting...`);
250
- return false;
126
+ warn("Directory not found at " + outputDirectory);
127
+ return;
251
128
  }
252
- const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf") || f.endsWith(".vrm") || f.endsWith(".fbx"));
253
- log(`${files.length} file(s) to process in ${outputDirectory}`);
129
+ const files = readdirSync(outputDirectory).filter(f => f.endsWith(".glb") || f.endsWith(".gltf"));
130
+ log(files.length + " file(s) to process");
254
131
 
255
132
  /** @type {null | ChildProcess} */
256
- let proc = null;
133
+ let sub = null;
257
134
 
258
- const cloudAccessToken = process.env.NEEDLE_CLOUD_TOKEN;
259
- const runInCloud = typeof cloudAccessToken === "string" && cloudAccessToken.length > 0;
260
- // if a user has defined the build pipeline settings object but not passed in a token we should print out some information
261
- // or perhaps log an error / prevent the build from running completely
262
- if (opts.buildPipeline && !runInCloud && process.env.CI) {
263
- warn(`No cloud access token found. Please set it via process.env.NEEDLE_CLOUD_TOKEN`);
264
- return false;
265
- }
266
-
267
-
268
- // put the processed files first in a temporary directory. They will be moved to the output directory at the end of the buildstep
269
- // this is so that processes like sveltekit-static-adapter can run first and does not override already compressed files
270
- const tempOutputPath = process.cwd() + "/node_modules/.needle/build-pipeline/output";
271
- if (existsSync(tempOutputPath)) {
272
- log("Removing temporary output directory at " + tempOutputPath);
273
- rmSync(tempOutputPath, { recursive: true, force: true });
274
- }
275
- mkdirSync(tempOutputPath, { recursive: true });
276
-
277
- /** @param {number} code */
278
- function onExit(code) {
279
- if (code === 0)
280
- buildPipelineTaskResults = {
281
- tempDirectory: tempOutputPath,
282
- outputDirectory: outputDirectory
283
- }
284
- }
285
-
286
- // allow running the build pipeline in the cloud. It requires and access token to be set in the vite.config.js
287
- // this can be set via e.g. process.env.NEEDLE_CLOUD_TOKEN
288
- if (runInCloud) {
289
- if (!cloudAccessToken || !(typeof cloudAccessToken === "string") || cloudAccessToken.length <= 0) {
290
- throw new Error("No cloud access token configured. Please set it via process.env.NEEDLE_CLOUD_TOKEN or in the vite.config.js");
291
- }
292
- let cmd = `npx --yes @needle-tools/cloud-cli@latest optimize "${outputDirectory}" --accessToken ${cloudAccessToken}`;
293
- const projectName = opts.buildPipeline?.projectName;
294
- if (projectName) {
295
- cmd += ` --name "${projectName}"`;
296
- }
297
- if (opts.buildPipeline?.verbose === true) {
298
- cmd += " --verbose";
299
- }
300
- cmd += " --outdir \"" + tempOutputPath + "\"";
301
- console.log("\n")
302
- log("Running compression in cloud ⛅");
303
- proc = exec(cmd);
304
- }
305
- else if (existsLocally) {
306
- const cmd = `needle-gltf transform "${outputDirectory}" \"${tempOutputPath}\"`;
135
+ if (existsLocally) {
136
+ const cmd = `needle-gltf transform "${outputDirectory}"`;
307
137
  log("Running command \"" + cmd + "\" at " + process.cwd() + "...");
308
- proc = exec(cmd, { cwd: installPath });
138
+ sub = exec(cmd, { cwd: installPath });
309
139
  }
310
140
  else {
311
- const version = opts.buildPipeline?.version || "latest";
312
- const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}" \"${tempOutputPath}\"`;
313
- log(`Running compression locally with version ${version}`);
314
- proc = exec(cmd);
141
+ const version = opts.buildPipelineVersion || "latest";
142
+ const cmd = `npx --yes @needle-tools/gltf-build-pipeline@${version} transform "${outputDirectory}"`;
143
+ log("Running command \"" + cmd);
144
+ sub = exec(cmd);
315
145
  }
316
- /** @param {any} data */
317
- function onLog(data) {
146
+ sub.stdout.on('data', data => {
318
147
  if (data.length <= 0) return;
319
148
  // ensure that it doesnt end with a newline
320
- while (data.endsWith("\n")) data = data.slice(0, -1);
321
- if (typeof data === "string") {
322
- if (data.startsWith("ERR:")) {
323
- console.error(data);
324
- return;
325
- }
326
- else if (data.startsWith("WARN:")) {
327
- console.warn(data);
328
- return;
329
- }
330
- // Ignore empty lines
331
- else if (data.trim().length <= 0) {
332
- return;
333
- }
334
- }
335
- log(data);
336
- }
337
- proc.stdout?.on('data', onLog);
338
- proc.stderr?.on('data', onLog);
149
+ if (data.endsWith("\n")) data = data.slice(0, -1);
150
+ console.log(data);
151
+ });
152
+ sub.stderr.on('data', console.error);
339
153
  return new Promise((resolve, reject) => {
340
- proc.on('exit', (code) => {
341
- onExit(code || 0);
154
+ sub.on('exit', (code) => {
342
155
  resolve(code === 0);
343
156
  });
344
157
  });
345
158
  }
159
+
160
+ function wait(ms) {
161
+ return new Promise((resolve, reject) => {
162
+ setTimeout(() => {
163
+ resolve();
164
+ }, ms);
165
+ });
166
+ }
plugins/common/buildinfo.js CHANGED
@@ -16,11 +16,11 @@
16
16
  totalsize: 0,
17
17
  files: []
18
18
  };
19
- console.log(`[needle-buildinfo] - Begin collecting files in \"${buildDirectory}\"`);
19
+ console.log("[needle-buildinfo] - Collect files in " + buildDirectory);
20
20
  recursivelyCollectFiles(buildDirectory, "", buildInfo);
21
21
  const buildInfoPath = `${buildDirectory}/needle.buildinfo.json`;
22
22
  const totalSizeInMB = buildInfo.totalsize / 1024 / 1024;
23
- console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB). Writing build info to \"${buildInfoPath}\"`);
23
+ console.log(`[needle-buildinfo] - Collected ${buildInfo.files.length} files (${totalSizeInMB.toFixed(2)} MB). Write info to \"${buildInfoPath}\"`);
24
24
  fs.writeFileSync(buildInfoPath, JSON.stringify(buildInfo));
25
25
  }
26
26
 
@@ -39,11 +39,10 @@
39
39
  }
40
40
  const newPath = path?.length <= 0 ? entry.name : `${path}/${entry.name}`;
41
41
  const newDirectory = `${directory}/${entry.name}`;
42
- console.log(`[needle-buildinfo] - Collect files in \"/${newPath}\"`);
42
+ console.log("[needle-buildinfo] - Collect files in " + newPath);
43
43
  recursivelyCollectFiles(newDirectory, newPath, info);
44
44
  } else {
45
45
  const relpath = `${path}/${entry.name}`;
46
- // console.log("[needle-buildinfo] - New File: " + relpath);
47
46
  const filehash = crypto.createHash('sha256');
48
47
  const fullpath = `${directory}/${entry.name}`;
49
48
  filehash.update(fs.readFileSync(fullpath));
plugins/vite/buildinfo.js CHANGED
@@ -1,38 +1,32 @@
1
1
  import { createBuildInfoFile } from '../common/buildinfo.js';
2
- import { waitForBuildPipelineToFinish } from './build-pipeline.js';
2
+ import { buildPipelineTask } from './build-pipeline.js';
3
3
  import { getOutputDirectory } from './config.js';
4
4
 
5
- let level = 0;
6
5
 
6
+
7
7
  /** Create a buildinfo file in the build directory
8
8
  * @param {import('../types').userSettings} userSettings
9
- * @returns {import('vite').Plugin | null}
9
+ * @returns {import('vite').Plugin}
10
10
  */
11
11
  export const needleBuildInfo = (command, config, userSettings) => {
12
12
 
13
- if (userSettings?.noBuildInfo) return null;
13
+ if (userSettings?.noBuildInfo) return;
14
14
 
15
15
  return {
16
16
  name: 'needle-buildinfo',
17
17
  apply: "build",
18
18
  enforce: "post",
19
- buildStart: () => {
20
- level++;
21
- },
22
19
  closeBundle: async () => {
23
- if (--level > 0) {
24
- console.log("[needle-buildinfo] - Skipped because of nested build");
25
- return;
26
- }
27
- const task = waitForBuildPipelineToFinish();
28
- if (task instanceof Promise) {
29
- console.log("[needle-buildinfo] - Waiting for build pipeline to finish");
30
- await task.catch(() => { }).finally(() => console.log("[needle-buildinfo] - Build pipeline finished!"));
31
- }
32
- // wait for gzip
33
- await delay(500);
34
- const buildDirectory = getOutputDirectory();
35
- createBuildInfoFile(buildDirectory);
20
+ return new Promise(async res => {
21
+ if (buildPipelineTask instanceof Promise) {
22
+ await buildPipelineTask.catch(() => { });
23
+ }
24
+ // wait for gzip
25
+ await delay(500);
26
+ const buildDirectory = getOutputDirectory();
27
+ createBuildInfoFile(buildDirectory);
28
+ res();
29
+ });
36
30
  }
37
31
  }
38
32
  }
plugins/vite/copyfiles.js CHANGED
@@ -1,52 +1,41 @@
1
1
 
2
2
  import { resolve, join, isAbsolute } from 'path'
3
- import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir, rmSync } from 'fs';
3
+ import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
4
4
  import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
5
- import { copyFilesSync } from '../common/files.js';
6
5
 
7
- const pluginName = "needle-copy-files";
8
6
 
9
-
10
7
  /** copy files on build from assets to dist
11
8
  * @param {import('../types').userSettings} userSettings
12
- * @returns {import('vite').Plugin | null}
13
9
  */
14
10
  export const needleCopyFiles = (command, config, userSettings) => {
15
11
 
16
12
  if (config?.noCopy === true || userSettings?.noCopy === true) {
17
- return null;
13
+ return;
18
14
  }
19
15
 
20
16
  return {
21
17
  name: 'needle-copy-files',
22
- // explicitly don't enforce post or pre because we want to run before the build-pipeline plugin
23
- enforce: "pre",
24
18
  buildStart() {
25
- return run("start", config);
19
+ return run(false, config);
26
20
  },
27
21
  closeBundle() {
28
- return run("end", config);
22
+ return run(true, config);
29
23
  },
30
24
  }
31
25
  }
32
26
 
33
- /**
34
- * @param {"start" | "end"} buildstep
35
- * @param {import('../types').userSettings} config
36
- */
37
- async function run(buildstep, config) {
38
- console.log(`[${pluginName}] - Copy files at ${buildstep}`);
27
+ async function run(isBuild, config) {
39
28
  const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
40
29
 
41
30
  const baseDir = process.cwd();
42
- const override = buildstep === "start";
31
+ const pluginName = "needle-copy-files";
43
32
 
44
33
  let assetsDirName = "assets";
45
34
  let outdirName = "dist";
46
35
 
47
36
  const needleConfig = tryLoadProjectConfig();
48
37
  if (needleConfig) {
49
- assetsDirName = needleConfig.assetsDirectory || assetsDirName;
38
+ assetsDirName = needleConfig.assetsDirectory;
50
39
  while (assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
51
40
 
52
41
  if (needleConfig.buildDirectory)
@@ -59,67 +48,60 @@
59
48
  if (existsSync(engineIncludeDir)) {
60
49
  console.log(`[${pluginName}] - Copy engine include to ${baseDir}/include`)
61
50
  const projectIncludeDir = resolve(baseDir, 'include');
62
- copyFilesSync(engineIncludeDir, projectIncludeDir);
51
+ copyRecursiveSync(engineIncludeDir, projectIncludeDir);
63
52
  }
64
53
  }
65
54
 
66
- const outDir = resolve(baseDir, outdirName);
67
- if (!existsSync(outDir)) {
68
- mkdirSync(outDir);
69
- }
55
+ if (isBuild) {
56
+ const outDir = resolve(baseDir, outdirName);
57
+ if (!existsSync(outDir)) {
58
+ mkdirSync(outDir);
59
+ }
70
60
 
71
- // copy a list of files or directories declared in build.copy = [] in the needle.config.json
72
- /*
73
- "build": {
74
- "copy": ["myFolder", "myFile.txt"]
75
- }
76
- */
77
- if (needleConfig?.build?.copy) {
78
- const arr = needleConfig.build.copy;
79
- for (let i = 0; i < arr.length; i++) {
80
- const entry = arr[i];
81
- if (Array.isArray(entry)) {
82
- console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
83
- continue;
61
+ // copy a list of files or directories declared in build.copy = [] in the needle.config.json
62
+ /*
63
+ "build": {
64
+ "copy": ["myFolder", "myFile.txt"]
65
+ }
66
+ */
67
+ if (needleConfig?.build?.copy) {
68
+ const arr = needleConfig.build.copy;
69
+ for (let i = 0; i < arr.length; i++) {
70
+ const entry = arr[i];
71
+ if (Array.isArray(entry)) {
72
+ console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
73
+ continue;
74
+ }
75
+ const src = resolve(baseDir, entry);
76
+ const dest = resolvePath(outDir, entry);
77
+ if (existsSync(src)) {
78
+ console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
79
+ copyRecursiveSync(src, dest);
80
+ }
84
81
  }
85
- const src = resolve(baseDir, entry);
86
- const dest = resolvePath(outDir, entry);
87
- if (existsSync(src) && dest) {
88
- console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
89
- copyFilesSync(src, dest, override);
90
- }
91
82
  }
92
- }
93
83
 
94
- // copy assets dir
95
- const assetsDir = resolve(baseDir, assetsDirName);
96
- if (existsSync(assetsDir)) {
97
- const targetDir = resolve(outDir, 'assets');
98
- // ensure that the target directory exists and is cleared if it already exists
99
- // otherwise we might run into issues where the build pipeline is running for already compressed files
100
- if (override && existsSync(targetDir)) {
101
- console.log(`[${pluginName}] - Clearing target directory \"${targetDir}\"`);
102
- rmSync(targetDir, { recursive: true, force: true });
84
+ // copy assets dir
85
+ const assetsDir = resolve(baseDir, assetsDirName);
86
+ if (existsSync(assetsDir)) {
87
+ console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
88
+ const targetDir = resolve(outDir, 'assets');
89
+ copyRecursiveSync(assetsDir, targetDir);
103
90
  }
104
- console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
105
- copyFilesSync(assetsDir, targetDir, override);
91
+ else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
92
+ // copy include dir
93
+ const includeDir = resolve(baseDir, 'include');
94
+ if (existsSync(includeDir)) {
95
+ console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
96
+ const targetDir = resolve(outDir, 'include');
97
+ copyRecursiveSync(includeDir, targetDir);
98
+ }
106
99
  }
107
- else console.log(`WARN: No assets directory found. Skipping copy of ${assetsDirName} resolved to ${assetsDir}`)
108
-
109
- // copy include dir
110
- const includeDir = resolve(baseDir, 'include');
111
- if (existsSync(includeDir)) {
112
- console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
113
- const targetDir = resolve(outDir, 'include');
114
- copyFilesSync(includeDir, targetDir, override);
115
- }
116
100
  }
117
101
 
118
102
  /** resolves relative or absolute paths to a path inside the out directory
119
103
  * for example D:/myFile.txt would resolve to outDir/myFile.txt
120
104
  * wherereas "some/relative/path" would become outDir/some/relative/path
121
- * @param {string} outDir
122
- * @param {string} pathValue
123
105
  */
124
106
  function resolvePath(outDir, pathValue) {
125
107
  if (isAbsolute(pathValue)) {
@@ -128,12 +110,29 @@
128
110
  var stats = exists && statSync(pathValue);
129
111
  if (stats.isDirectory()) {
130
112
  const dirName = pathValue.replaceAll('\\', '/').split('/').pop();
131
- if (!dirName) return null;
132
113
  return resolve(outDir, dirName);
133
114
  }
134
115
  const fileName = pathValue.replaceAll('\\', '/').split('/').pop();
135
- if (!fileName) return null;
136
116
  return resolve(outDir, fileName);
137
117
  }
138
118
  return resolve(outDir, pathValue);
139
- }
119
+ }
120
+
121
+ function copyRecursiveSync(src, dest) {
122
+ if (dest === null) {
123
+ console.log(`[${pluginName}] - Copy ${src} to ${dest} - dest is null`)
124
+ return;
125
+ }
126
+ var exists = existsSync(src);
127
+ var stats = exists && statSync(src);
128
+ var isDirectory = exists && stats.isDirectory();
129
+ if (isDirectory) {
130
+ if (!existsSync(dest))
131
+ mkdirSync(dest, { recursive: true });
132
+ readdirSync(src).forEach(function (childItemName) {
133
+ copyRecursiveSync(join(src, childItemName), join(dest, childItemName));
134
+ });
135
+ } else {
136
+ copyFileSync(src, dest);
137
+ }
138
+ };
plugins/vite/dependencies.js CHANGED
@@ -7,90 +7,28 @@
7
7
  * @param {import('../types').userSettings} userSettings
8
8
  */
9
9
  export const needleDependencies = (command, config, userSettings) => {
10
-
11
- const handleChunks = true;
12
-
13
10
  /**
14
11
  * @type {import('vite').Plugin}
15
12
  */
16
13
  return {
17
14
  name: 'needle-dependencies',
18
15
  enforce: 'pre',
19
- /**
20
- * @param {import('vite').UserConfig} config
21
- */
22
- config: (config, env) => {
23
- handleOptimizeDeps(config);
24
- handleManualChunks(config);
16
+ config: (config) => {
17
+ if (config.optimizeDeps?.include?.includes("three-mesh-bvh")) {
18
+ console.log("[needle-dependencies] three-mesh-bvh is included in the optimizeDeps.include array. This may cause issues with the worker import.");
19
+ }
20
+ else {
21
+ if (!config.optimizeDeps) {
22
+ config.optimizeDeps = {};
23
+ }
24
+ if (!config.optimizeDeps.exclude) {
25
+ config.optimizeDeps.exclude = [];
26
+ }
27
+ console.log("[needle-dependencies] Adding three-mesh-bvh to the optimizeDeps.exclude array.");
28
+ // This needs to be excluded from optimization because otherwise the worker import fails
29
+ // three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker
30
+ config.optimizeDeps.exclude.push("three-mesh-bvh");
31
+ }
25
32
  }
26
33
  }
27
34
  }
28
-
29
- /**
30
- * @param {import('vite').UserConfig} config
31
- */
32
- function handleOptimizeDeps(config) {
33
- if (config.optimizeDeps?.include?.includes("three-mesh-bvh")) {
34
- console.log("[needle-dependencies] three-mesh-bvh is included in the optimizeDeps.include array. This may cause issues with the worker import.");
35
- }
36
- else {
37
- if (!config.optimizeDeps) {
38
- config.optimizeDeps = {};
39
- }
40
- if (!config.optimizeDeps.exclude) {
41
- config.optimizeDeps.exclude = [];
42
- }
43
- console.log("[needle-dependencies] Adding three-mesh-bvh to the optimizeDeps.exclude array.");
44
- // This needs to be excluded from optimization because otherwise the worker import fails
45
- // three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker
46
- config.optimizeDeps.exclude.push("three-mesh-bvh");
47
- }
48
- }
49
-
50
- /**
51
- * @param {import('vite').UserConfig} config
52
- */
53
- function handleManualChunks(config) {
54
- if (!config.build) {
55
- config.build = {};
56
- }
57
- if (!config.build.rollupOptions) {
58
- config.build.rollupOptions = {};
59
- }
60
- if (!config.build.rollupOptions.output) {
61
- config.build.rollupOptions.output = {};
62
- }
63
-
64
- const rollupOutput = config.build.rollupOptions.output;
65
-
66
- if (Array.isArray(rollupOutput)) {
67
- // append the manualChunks function to the array
68
- console.log("[needle-dependencies] registering manualChunks");
69
- rollupOutput.push({
70
- manualChunks: needleManualChunks
71
- })
72
- }
73
- else {
74
- if (rollupOutput.manualChunks) {
75
- // if the user has already defined manualChunks, we don't want to overwrite it
76
- console.log("[needle-dependencies] manualChunks already defined");
77
- }
78
- else {
79
- console.log("[needle-dependencies] registering manualChunks");
80
- rollupOutput.manualChunks = needleManualChunks;
81
- }
82
- }
83
-
84
- function needleManualChunks(id) {
85
- // Push the pmndrs postprocessing package into a separate postprocessing chunk
86
- if (id.includes("node_modules/postprocessing/" || id.includes("node_modules/n8ao"))) {
87
- return "postprocessing";
88
- }
89
- else if (id.includes("node_modules/three-mesh-ui")) {
90
- return "three-mesh-ui";
91
- }
92
- else if (id.includes("@dimforge/rapier3d")) {
93
- return "rapier3d";
94
- }
95
- }
96
- }
plugins/common/files.js DELETED
@@ -1,32 +0,0 @@
1
- import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
2
- import { join } from "path";
3
-
4
- /**
5
- * @param {string} src
6
- * @param {string} dest
7
- * @param {boolean} override
8
- * @param {{count:number} | undefined} ctx
9
- */
10
- export function copyFilesSync(src, dest, override = true, ctx = undefined) {
11
- if (dest === null) {
12
- console.log(`[needle-copy] - Copy ${src} to ${dest} - dest is null`)
13
- return;
14
- }
15
- let exists = existsSync(src);
16
- let stats = exists && statSync(src);
17
- let isDirectory = exists && typeof stats != "boolean" && stats.isDirectory();
18
- if (isDirectory) {
19
- if (!existsSync(dest))
20
- mkdirSync(dest, { recursive: true });
21
- readdirSync(src).forEach(function (childItemName) {
22
- // recurse
23
- copyFilesSync(join(src, childItemName), join(dest, childItemName), override, ctx);
24
- });
25
- }
26
- else if (override || !existsSync(dest)) {
27
- if (ctx) {
28
- ctx.count += 1;
29
- }
30
- copyFileSync(src, dest);
31
- }
32
- };
plugins/vite/index.js CHANGED
@@ -96,7 +96,7 @@
96
96
  }
97
97
  * ```
98
98
  * @param {string} command
99
- * @param {import('../types/index.js').userSettings} userSettings
99
+ * @param {import('../types').userSettings} userSettings
100
100
  */
101
101
  export const needlePlugins = async (command, config, userSettings) => {
102
102
 
plugins/common/timers.js DELETED
@@ -1,8 +0,0 @@
1
-
2
-
3
- /**
4
- * @param {number} ms
5
- */
6
- export function delay(ms) {
7
- return new Promise(res => setTimeout(res, ms));
8
- }
src/engine/js-extensions/Camera.ts CHANGED
@@ -9,7 +9,8 @@
9
9
  const changed = val !== this._fov;
10
10
  this._fov = val;
11
11
  if (changed && this.view !== undefined) this.updateProjectionMatrix();
12
- }
12
+ },
13
+ configurable: true
13
14
  });
14
15
 
15
16
  Object.defineProperty(PerspectiveCamera.prototype, "near", {
@@ -20,7 +21,8 @@
20
21
  const changed = val !== this._near;
21
22
  this._near = val;
22
23
  if (changed && this.view !== undefined) this.updateProjectionMatrix();
23
- }
24
+ },
25
+ configurable: true
24
26
  });
25
27
 
26
28
  Object.defineProperty(PerspectiveCamera.prototype, "far", {
@@ -31,5 +33,6 @@
31
33
  const changed = val !== this._far;
32
34
  this._far = val;
33
35
  if (changed && this.view !== undefined) this.updateProjectionMatrix();
34
- }
36
+ },
37
+ configurable: true
35
38
  });
src/engine/engine_element.ts CHANGED
@@ -140,6 +140,7 @@
140
140
  width: max(600px, 100%);
141
141
  height: max(300px, 100%);
142
142
  touch-action: none;
143
+
143
144
  -webkit-tap-highlight-color: transparent;
144
145
  }
145
146
 
@@ -156,10 +157,16 @@
156
157
  :host canvas {
157
158
  position: absolute;
158
159
  user-select: none;
160
+ -webkit-user-select: none;
161
+
159
162
  /** allow touch panning but no pinch zoom **/
160
163
  /** but this doesnt work yet:
161
164
  * touch-action: pan-x, pan-y;
162
165
  **/
166
+
167
+ -webkit-touch-callout: none;
168
+ -webkit-user-drag: none;
169
+ -webkit-user-modify: none;
163
170
  }
164
171
  :host .content {
165
172
  position: absolute;
src/engine/engine_physics.ts CHANGED
@@ -299,12 +299,14 @@
299
299
 
300
300
  private intersect(raycaster: Raycaster, objects: Object3D[], results: Intersection[], options: IRaycastOptions) {
301
301
  for (const obj of objects) {
302
+
302
303
  // handle case where null or undefined object is in the scene
303
304
  if (!obj) continue;
304
305
  // dont raycast invisible objects
305
306
  if (obj.visible === false) continue;
306
307
 
307
308
  if (Gizmos.isGizmo(obj)) continue;
309
+
308
310
  // dont raycast object if it's a line and the line threshold is < 0
309
311
  if (options.lineThreshold !== undefined && options.lineThreshold < 0) {
310
312
  if (obj instanceof Line) {
@@ -312,20 +314,13 @@
312
314
  }
313
315
  }
314
316
 
315
- const mesh = obj as Mesh;
317
+ let shouldIntersectObject = true;
318
+ const mesh = obj as Mesh | SkinnedMesh;
316
319
  const geo = mesh.geometry;
317
320
 
318
- let shouldIntersectObject = true;
319
- if (!geo) {
320
- shouldIntersectObject = false;
321
- }
322
- // check if the geometry is valid
323
- else if (shouldIntersectObject && !canRaycastGeometry(geo)) {
324
- shouldIntersectObject = false;
325
- }
326
-
327
- if (shouldIntersectObject && options.testObject) {
328
- const testResult = options.testObject(obj);
321
+ // We need to run this first because of "EventSystem.testObject" implementation
322
+ if (options.testObject) {
323
+ const testResult = options.testObject?.(obj);
329
324
  if (testResult === false) {
330
325
  continue;
331
326
  }
@@ -335,6 +330,17 @@
335
330
  }
336
331
 
337
332
  if (shouldIntersectObject) {
333
+ if (!geo) {
334
+ shouldIntersectObject = false;
335
+ }
336
+ // check if the geometry is valid
337
+ else if (!canRaycastGeometry(geo)) {
338
+ shouldIntersectObject = false;
339
+ }
340
+ }
341
+
342
+
343
+ if (shouldIntersectObject) {
338
344
  const raycastMesh = getRaycastMesh(obj);
339
345
  if (raycastMesh) mesh.geometry = raycastMesh;
340
346
  const lastResultsCount = results.length;
@@ -380,15 +386,6 @@
380
386
  const mesh = obj as Mesh;
381
387
  const geo = mesh.geometry;
382
388
 
383
- // check if geometry exists
384
- if (!geo) {
385
- shouldIntersectObject = false;
386
- }
387
- // check if the geometry is valid
388
- else if (!canRaycastGeometry(geo)) {
389
- shouldIntersectObject = false;
390
- }
391
-
392
389
  if (shouldIntersectObject && shouldRaycast) {
393
390
  const testResult = shouldRaycast(obj);
394
391
  if (testResult === false) {
@@ -399,7 +396,16 @@
399
396
  }
400
397
  }
401
398
 
399
+ // check if geometry exists
400
+ if (!geo) {
401
+ shouldIntersectObject = false;
402
+ }
403
+ // check if the geometry is valid
404
+ else if (!canRaycastGeometry(geo)) {
405
+ shouldIntersectObject = false;
406
+ }
402
407
 
408
+
403
409
  if (shouldIntersectObject) {
404
410
  if (useBvh) {
405
411
  const sphere = this.sphere;
src/engine/engine_utils_screenshot.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { Camera, Color, ColorRepresentation, PerspectiveCamera } from "three";
2
2
 
3
3
  import { Renderer } from "../engine-components/Renderer.js";
4
- import { RGBAColor } from "./js-extensions/index.js";
5
4
  import { getComponentsInChildren } from "./engine_components.js";
6
5
  import { ContextRegistry } from "./engine_context_registry.js";
7
6
  import { Context } from "./engine_setup.js";
8
7
  import { ICamera } from "./engine_types.js";
8
+ import { RGBAColor } from "./js-extensions/index.js";
9
9
 
10
10
  declare type ScreenshotImageMimeType = "image/webp" | "image/png";
11
11
 
src/engine-components/ui/EventSystem.ts CHANGED
@@ -235,6 +235,9 @@
235
235
  * For example if an event component has only an onPointerClick method we don't need to raycast during movement events
236
236
  * */
237
237
  private shouldRaycastObject = (obj: Object3D): RaycastTestObjectReturnType => {
238
+ // TODO: this implementation below should be removed and we should regularly raycast objects in the scene unless marked as "do not raycast"
239
+ // with the introduction of the mesh-bvh based raycasting the performance impact should be greatly reduced. But this needs further testing
240
+
238
241
  // check if this object is actually a UI shadow hierarchy object
239
242
  let uiOwner: Object3D | null = null;
240
243
  const isUI = isUIObject(obj);
src/engine/analytics/index.ts DELETED
@@ -1,11 +0,0 @@
1
- import { LCP } from "./lcp.js";
2
-
3
- /**
4
- * @param args - The arguments to initialize the performance analytics with.
5
- */
6
- export module NeedleEnginePerformanceAnalytics {
7
- export function init(...args: Array<"lcp">) {
8
- if (args.includes("lcp"))
9
- LCP.observe();
10
- }
11
- }
src/engine/analytics/lcp.ts DELETED
@@ -1,35 +0,0 @@
1
-
2
- // https://web.dev/articles/lcp
3
- export module LCP {
4
- export function observe() {
5
- const observer = new PerformanceObserver((list) => {
6
- let perfEntries = list.getEntries();
7
- let lastEntry = perfEntries[perfEntries.length - 1] as PerformanceEntry & { url?: string };
8
- // Process the latest candidate for largest contentful paint
9
- console.log('LCP candidate:', lastEntry);
10
-
11
- const el = createElementFromUrl(lastEntry.url);
12
- if (el) document.body.appendChild(el);
13
- });
14
- observer.observe({ entryTypes: ['largest-contentful-paint'] });
15
- }
16
-
17
-
18
- function createElementFromUrl(str: string | null | undefined) {
19
- if (!str) return null;
20
-
21
- if (!str.startsWith('data:image/')) {
22
- return null;
23
- }
24
- else if (!str.includes("base64")) {
25
- return null;
26
- }
27
- const img = document.createElement('img');
28
- img.src = str;
29
- img.onerror = err => {
30
- console.error(err);
31
- img.remove();
32
- };
33
- return img;
34
- }
35
- }
src/asap/needle-asap.ts CHANGED
@@ -74,22 +74,8 @@
74
74
 
75
75
 
76
76
  function insertTemporaryContentWhileEngineHasntLoaded(needleEngineElement: HTMLElement) {
77
- // if (needleEngineHasLoaded()) return;
77
+ if (needleEngineHasLoaded()) return;
78
78
 
79
- const asapdiv = document.createElement("div");
80
- asapdiv.style.cssText = `
81
- position: absolute;
82
- top: 0;
83
- left: 0;
84
- width: 100%;
85
- height: 100%;
86
- z-index: -1;
87
- display: flex;
88
- justify-content: center;
89
- align-items: center;
90
- pointer-events: none;
91
- `
92
-
93
79
  const img = document.createElement("img");
94
80
 
95
81
  // if a custom logo is assigned we should use this here
@@ -99,22 +85,24 @@
99
85
  img.src = customLogoUrl;
100
86
  else
101
87
  img.src = needleLogoOnlySVG;
88
+ img.style.position = "absolute";
89
+ img.style.top = "50%";
90
+ img.style.left = "50%";
91
+ img.style.transform = "translate(-50%, -50%)";
102
92
  img.style.zIndex = "-10";
103
- img.style.width = "140px";
93
+ img.style.pointerEvents = "none";
94
+ img.style.width = "33px";
104
95
  img.style.height = "auto";
105
- img.style.opacity = "1";
96
+ img.style.filter = "grayscale(100%)";
97
+ img.style.opacity = "0";
106
98
  img.style.textShadow = "0 0 10px 0px rgba(0,0,0,.5)";
99
+ needleEngineElement.appendChild(img);
107
100
 
108
- asapdiv.appendChild(img);
109
- needleEngineElement.appendChild(asapdiv);
110
-
111
101
  // animation to pulsate
112
- // don't animate to opacity 0 because for LCP we need to keep the element in the DOM and visible to the user
113
- // see https://web.dev/articles/lcp#what-elements-are-considered
114
102
  img.animate([
115
- { opacity: .2 },
116
- { opacity: .7 },
117
- { opacity: .2 }
103
+ { opacity: 0 },
104
+ { opacity: .5 },
105
+ { opacity: 0 }
118
106
  ], {
119
107
  duration: 3000,
120
108
  iterations: Infinity
src/engine/webcomponents/needle menu/needle-menu.ts CHANGED
@@ -595,7 +595,7 @@
595
595
  <slot name="end"></slot>
596
596
  </div>
597
597
  </div>
598
- <div class="logo">
598
+ <div style="user-select:none" class="logo">
599
599
  <span class="madewith notranslate">powered by</span>
600
600
  </div>
601
601
  </div>
plugins/types/needleConfig.d.ts CHANGED
@@ -22,7 +22,4 @@
22
22
  assetsDirectory?: string;
23
23
  scriptsDirectory?: string;
24
24
  codegenDirectory?: string;
25
- build?: {
26
- copy?: Array<string>;
27
- }
28
25
  }
src/engine/codegen/register_types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable */
2
2
  import { TypeStore } from "./../engine_typestore.js"
3
-
3
+
4
4
  // Import types
5
5
  import { __Ignore } from "../../engine-components/codegen/components.js";
6
6
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
@@ -220,7 +220,7 @@
220
220
  import { XRFlag } from "../../engine-components/webxr/XRFlag.js";
221
221
  import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
222
222
  import { XRState } from "../../engine-components/webxr/XRFlag.js";
223
-
223
+
224
224
  // Register types
225
225
  TypeStore.add("__Ignore", __Ignore);
226
226
  TypeStore.add("ActionBuilder", ActionBuilder);
plugins/types/userconfig.d.ts CHANGED
@@ -5,9 +5,6 @@
5
5
  webpack: object | undefined
6
6
  }
7
7
 
8
- /**
9
- * Settings for the Needle plugin
10
- */
11
8
  export type userSettings = {
12
9
 
13
10
  /** disable needle asap plugin */
@@ -23,8 +20,6 @@
23
20
 
24
21
  /** disable automatic copying of files to include and output directory (dist) */
25
22
  noCopy?: boolean;
26
- /** When enabled the needle-engine include directory will be copied */
27
- copyIncludesFromEngine?: boolean;
28
23
  /** set to false to tree-shake rapier physics engine to the reduce bundle size */
29
24
  useRapier?: boolean;
30
25
  noDependencyWatcher?: boolean;
@@ -49,25 +44,12 @@
49
44
 
50
45
  /** Set to true to disable the needle build pipeline (running compression and optimization as a postprocessing step on the exported glTF files) */
51
46
  noBuildPipeline?: boolean;
47
+ /** Set to a specific version of the Needle Build Pipeline.
48
+ * @default "latest"
49
+ * @example "2.2.0-alpha"
50
+ */
51
+ buildPipelineVersion?: string;
52
52
 
53
- /**
54
- * Use to configure optimized builds
55
- */
56
- buildPipeline?: {
57
- /** Set to false to prevent the build pipeline from running */
58
- enabled?: boolean,
59
- /** Set a project name (cloud only) */
60
- projectName?: string,
61
- /** Enable for verbose log output */
62
- verbose?: boolean,
63
-
64
- /** Set to a specific version of the Needle Build Pipeline.
65
- * @default "latest"
66
- * @example "2.2.0-alpha"
67
- */
68
- version?: string;
69
- }
70
-
71
53
  /** required for @serializable https://github.com/vitejs/vite/issues/13736 */
72
54
  vite44Hack?: boolean;
73
55
 
src/engine/engine.ts ADDED
@@ -0,0 +1,14 @@
1
+ import "./engine_hot_reload.js";
2
+ import "./tests/test_utils.js";
3
+
4
+ import { RGBAColor } from "../engine-components/js-extensions/RGBAColor.js";
5
+ import * as engine_scenetools from "./engine_scenetools.js";
6
+ import * as engine_setup from "./engine_setup.js";
7
+
8
+ const engine : any = {
9
+ ...engine_setup,
10
+ ...engine_scenetools,
11
+ RGBAColor,
12
+ };
13
+
14
+ export { engine as engine }