@needle-tools/engine 5.0.5 → 5.0.6-next.bb467e1

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 (33) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-CwycbG-K.umd.cjs → needle-engine.bundle-BPvphEEJ.umd.cjs} +75 -75
  4. package/dist/{needle-engine.bundle-DLNQOFNZ.js → needle-engine.bundle-CNoLozgc.js} +1744 -1703
  5. package/dist/{needle-engine.bundle-yYWZqy6w.min.js → needle-engine.bundle-DNW6Q7aB.min.js} +83 -83
  6. package/dist/needle-engine.d.ts +14 -1
  7. package/dist/needle-engine.js +2 -2
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  11. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  12. package/lib/engine-components/DragControls.d.ts +7 -0
  13. package/lib/engine-components/DragControls.js +19 -0
  14. package/lib/engine-components/DragControls.js.map +1 -1
  15. package/lib/engine-components/OrbitControls.js +16 -11
  16. package/lib/engine-components/OrbitControls.js.map +1 -1
  17. package/lib/engine-components/ui/Text.d.ts +8 -1
  18. package/lib/engine-components/ui/Text.js +29 -14
  19. package/lib/engine-components/ui/Text.js.map +1 -1
  20. package/lib/engine-components/web/CursorFollow.js +21 -12
  21. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  22. package/package.json +2 -2
  23. package/plugins/common/worker.js +9 -4
  24. package/plugins/vite/asap.js +17 -8
  25. package/plugins/vite/dependencies.js +29 -0
  26. package/plugins/vite/local-files-core.js +3 -3
  27. package/plugins/vite/local-files-utils.d.ts +3 -1
  28. package/plugins/vite/local-files-utils.js +29 -5
  29. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  30. package/src/engine-components/DragControls.ts +18 -2
  31. package/src/engine-components/OrbitControls.ts +18 -9
  32. package/src/engine-components/ui/Text.ts +43 -18
  33. package/src/engine-components/web/CursorFollow.ts +21 -13
@@ -266,19 +266,28 @@ export class CursorFollow extends Behaviour {
266
266
  this.gameObject.worldPosition = newPosition;
267
267
  }
268
268
  if (this.snapToSurface) {
269
- ray.origin = _position;
270
- ray.direction = rayDirection.multiplyScalar(-1);
271
- const hits = this.context.physics.raycastFromRay(ray);
272
- if (hits?.length) {
273
- const hit = hits[0];
274
- if (this.damping > 0) {
275
- this.gameObject.worldPosition = _position.lerp(hit.point, this.context.time.deltaTime / this.damping);
276
- }
277
- else {
278
- this.gameObject.worldPosition = hit.point;
269
+ ray.origin = cameraPosition;
270
+ ray.direction = rayDirection;
271
+ const hits = this.context.physics.raycastFromRay(ray, {
272
+ testObject: obj => {
273
+ return obj !== this.gameObject && !this.gameObject.contains(obj) ? true : false;
279
274
  }
280
- if (debug) {
281
- Gizmos.DrawLine(hit.point, hit.normal.add(hit.point), 0x00FF00);
275
+ });
276
+ if (hits?.length) {
277
+ // Get the first hit that is not a child of *this* object. Because we do not want to lerp to a surface that is part of the object itself, since that would result in *this* object moving closer and closer to the camera as it tries to snap to itself
278
+ const hit = hits[0]; //.find(h => !this.gameObject.contains(h.object));
279
+ if (hit) {
280
+ if (this.damping > 0) {
281
+ this.gameObject.worldPosition = _position.lerp(hit.point, this.context.time.deltaTime / this.damping);
282
+ // this._distance = this.gameObject.worldPosition.distanceTo(cameraPosition);
283
+ }
284
+ else {
285
+ this.gameObject.worldPosition = hit.point;
286
+ // this._distance = hit.point.distanceTo(cameraPosition);
287
+ }
288
+ if (debug) {
289
+ Gizmos.DrawLine(hit.point, hit.normal.add(hit.point), 0x00FF00);
290
+ }
282
291
  }
283
292
  }
284
293
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CursorFollow.js","sourceRoot":"","sources":["../../../src/engine-components/web/CursorFollow.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,gDAAgD,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH,MAAM,OAAO,YAAa,SAAQ,SAAS;IAEvC,+BAA+B;IAC/B,MAAM,CAAU,IAAI,GAAG,cAAc,CAAC;IAEtC;;;;;;;;;;;;;;;;OAgBG;IAEH,OAAO,GAAW,CAAC,CAAC;IAEpB;;;;;;;;;;;;;;;;;;;;OAoBG;IAEH,WAAW,GAAY,IAAI,CAAC;IAE5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IAEH,YAAY,GAAY,IAAI,CAAC;IAE7B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IAEH,aAAa,GAAY,KAAK,CAAC;IAGvB,SAAS,GAAW,CAAC,CAAC,CAAC;IAE/B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,QAAiB,KAAK;QACjC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO;QACX,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACrG,CAAC;IAED,gBAAgB;IAChB,KAAK;QACD,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IACxB,CAAC;IAED,gBAAgB;IAChB,QAAQ;QACJ,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QACpB,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAChE,CAAC;IACD,gBAAgB;IAChB,SAAS;QACL,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IACnE,CAAC;IAEO,MAAM,GAAG,CAAC,CAAC;IACX,MAAM,GAAG,CAAC,CAAC;IAEX,cAAc,GAAG,CAAC,CAAe,EAAE,EAAE;QACzC,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;QACpB,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,CAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC,CAAA;IAGD,gBAAgB;IAChB,UAAU;QACN,8DAA8D;QAC9D,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QAEhF,yEAAyE;QACzE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;QACvC,MAAM,cAAc,GAAG,MAAM,CAAC,aAAa,CAAC;QAE5C,iDAAiD;QACjD,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9D,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,SAAS,EAAE,CAAC;QAE7C,oDAAoD;QACpD,MAAM,WAAW,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACnG,IAAI,SAAS,GAAG,WAAW,CAAC;QAG5B,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;YAC1C,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YAClE,IAAI,CAAC,UAAU,CAAC,aAAa,GAAG,GAAG,CAAC;YACpC,SAAS,GAAG,GAAG,CAAC;QACpB,CAAC;aACI,CAAC;YACF,IAAI,CAAC,UAAU,CAAC,aAAa,GAAG,WAAW,CAAC;QAChD,CAAC;QAGD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;YACvB,GAAG,CAAC,SAAS,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YACtD,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;gBACf,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;oBACnB,IAAI,CAAC,UAAU,CAAC,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC1G,CAAC;qBACI,CAAC;oBACF,IAAI,CAAC,UAAU,CAAC,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC;gBAC9C,CAAC;gBAED,IAAG,KAAK,EAAE,CAAC;oBACP,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACrE,CAAC;YACL,CAAC;QACL,CAAC;IAEL,CAAC;;AAlMD;IADC,YAAY,EAAE;6CACK;AAwBpB;IADC,YAAY,EAAE;iDACa;AAyB5B;IADC,YAAY,EAAE;kDACc;AA6B7B;IADC,YAAY,EAAE;mDACgB;AAwHnC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"CursorFollow.js","sourceRoot":"","sources":["../../../src/engine-components/web/CursorFollow.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "5.0.5",
3
+ "version": "5.0.6-next.bb467e1",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.",
5
5
  "main": "dist/needle-engine.min.js",
6
6
  "exports": {
@@ -204,4 +204,4 @@
204
204
  "module": "lib/needle-engine.js",
205
205
  "typings": "lib/needle-engine.d.ts",
206
206
  "types": "lib/needle-engine.d.ts"
207
- }
207
+ }
@@ -1,3 +1,4 @@
1
+ import path from 'path';
1
2
  import { needleLog } from '../vite/logging.js';
2
3
 
3
4
 
@@ -53,13 +54,17 @@ export function rollupFixWorkerImport(opts = { logFail: true }) {
53
54
  // console.log("WORKER?", url)
54
55
  if (url?.startsWith("/")) {
55
56
  needleLog("rollup", `Rewrite worker import in ${chunk.fileName}`, "log", { leadingNewline: true, dimBody: false });
56
- // Make url file-relative
57
- const newUrl = url.replace(/^\//, "");
57
+ // Compute the relative path from the chunk's directory to the worker file.
58
+ // url is absolute from build root (e.g. "/assets/worker.js") and the chunk
59
+ // may itself be inside assets/ — using path.posix.relative avoids producing
60
+ // a double "assets/assets/" path.
61
+ const workerPath = url.replace(/^\//, "");
62
+ const chunkDir = path.posix.dirname(chunk.fileName);
63
+ const relativeUrl = path.posix.relative(chunkDir, workerPath) || workerPath;
58
64
  // For CORS issues we need to use importScripts: https://linear.app/needle/issue/NE-6572#comment-ea5dc65e
59
- const output = `/* new-worker */ new Worker(URL.createObjectURL(new Blob(["import '" + \`\${new URL('./${newUrl}', import.meta.url).toString()}\` + "';"], { type: 'text/javascript' }))`;
65
+ const output = `/* new-worker */ new Worker(URL.createObjectURL(new Blob(["import '" + \`\${new URL('./${relativeUrl}', import.meta.url).toString()}\` + "';"], { type: 'text/javascript' }))`;
60
66
  needleLog("rollup", "Did rewrite worker output to: " + output, "log", { leadingNewline: true });
61
67
  return output;
62
- // return `new Worker(new URL("./${newUrl}", import.meta.url)`;
63
68
  }
64
69
  return match;
65
70
  });
@@ -50,7 +50,7 @@ export async function needleAsap(command, config, userSettings) {
50
50
  const tags = [];
51
51
 
52
52
  try {
53
- generateGltfPreloadLinks(config, html, tags);
53
+ generateGltfPreloadLinks(config, html, tags, viteConfig?.base);
54
54
  }
55
55
  catch (err) {
56
56
  console.error("Error generating gltf preload links", err);
@@ -173,18 +173,21 @@ function fixMainTs() {
173
173
  * @param {import('vite').ResolvedConfig} _config
174
174
  * @param {import('vite').HtmlTagDescriptor[]} tags
175
175
  */
176
- function generateScriptPreloadLinks(_config, tags) {
176
+ function generateScriptPreloadLinks(config, tags) {
177
177
  try {
178
+ const base = config.base || '/';
178
179
  const chunks = preloadScriptPaths;
179
180
  // console.log("ASAP", chunks)
180
181
  if (chunks.length > 0) {
181
182
  for (const chunk of chunks) {
183
+ // Apply base path so preload hrefs resolve correctly under SPA routing
184
+ const href = chunk.startsWith('./') ? base + chunk.slice(2) : chunk;
182
185
  tags.push({
183
186
  tag: 'link',
184
187
  attrs: {
185
188
  rel: "modulepreload",
186
189
  as: "script",
187
- href: chunk,
190
+ href: href,
188
191
  }
189
192
  });
190
193
  }
@@ -203,15 +206,16 @@ const codegenRegex = /\"(?<gltf>.+(.glb|.gltf)(\?.*)?)\"/gm;
203
206
  /**
204
207
  * @param {import('../types').needleConfig} config
205
208
  * @param {string} html
206
- * @param {import('vite').HtmlTagDescriptor[]} tags
209
+ * @param {import('vite').HtmlTagDescriptor[]} tags
210
+ * @param {string} [base]
207
211
  **/
208
- function generateGltfPreloadLinks(config, html, tags) {
212
+ function generateGltfPreloadLinks(config, html, tags, base) {
209
213
 
210
214
  // TODO: try to get the <needle-engine src> element src attribute and preload that
211
215
  const needleEngineMatches = tryParseNeedleEngineSrcAttributeFromHtml(html);
212
216
  if (needleEngineMatches?.length) {
213
217
  for (const item of needleEngineMatches) {
214
- insertPreloadLink(tags, item, "model/gltf+json");
218
+ insertPreloadLink(tags, item, "model/gltf+json", base);
215
219
  }
216
220
  }
217
221
 
@@ -246,7 +250,7 @@ function generateGltfPreloadLinks(config, html, tags) {
246
250
  }
247
251
  }
248
252
  needleLog("needle:asap", `Insert glTF preload link: ${value}`);
249
- insertPreloadLink(tags, value, "model/gltf+json");
253
+ insertPreloadLink(tags, value, "model/gltf+json", base);
250
254
  }
251
255
  }
252
256
  }
@@ -258,9 +262,14 @@ function generateGltfPreloadLinks(config, html, tags) {
258
262
  * @param {import('vite').HtmlTagDescriptor[]} tags
259
263
  * @param {string} href
260
264
  * @param {string} type
265
+ * @param {string} [base]
261
266
  */
262
- function insertPreloadLink(tags, href, type) {
267
+ function insertPreloadLink(tags, href, type, base) {
263
268
  if (!href) return;
269
+ // Apply base path so preload hrefs resolve correctly under SPA routing
270
+ if (base && !href.startsWith('http') && !href.startsWith('/')) {
271
+ href = base + (href.startsWith('./') ? href.slice(2) : href);
272
+ }
264
273
  tags.push({
265
274
  tag: 'link',
266
275
  attrs: {
@@ -61,6 +61,35 @@ export function needleDependencies(command, config, userSettings) {
61
61
  }
62
62
  },
63
63
  },
64
+ // Vite 8's optimizer rebases `new URL(specifier, import.meta.url)` paths
65
+ // inside pre-bundled dependencies (PR #21434), but only handles truly relative
66
+ // paths (./ ../). Bare-specifier paths like `three-mesh-bvh/src/workers/...`
67
+ // are treated as relative to the *source file* that contained the `new URL()`
68
+ // call, producing a wrong URL such as:
69
+ // /node_modules/@needle-tools/engine/.../three-mesh-bvh/src/workers/generateMeshBVH.worker.js
70
+ // This middleware rewrites those broken URLs so the dev server finds the file at
71
+ // its real location in node_modules.
72
+ {
73
+ name: 'needle:worker-url-rewrite',
74
+ configureServer(server) {
75
+ const rewritePackages = ['three-mesh-bvh'];
76
+ server.middlewares.use((req, _res, next) => {
77
+ if (req.url) {
78
+ for (const pkg of rewritePackages) {
79
+ const marker = `/${pkg}/`;
80
+ if (req.url.includes(marker) && !req.url.startsWith(`/node_modules/${pkg}/`)) {
81
+ const idx = req.url.indexOf(marker);
82
+ const rewritten = '/node_modules' + req.url.slice(idx);
83
+ needleLog('needle-dependencies', `Rewriting worker URL → ${rewritten}`);
84
+ req.url = rewritten;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ next();
90
+ });
91
+ },
92
+ },
64
93
  ]
65
94
  }
66
95
 
@@ -291,7 +291,7 @@ export function needleMakeFilesLocal(command, _config, userSettings) {
291
291
  failedDownloads,
292
292
  localizationStats,
293
293
  }, activeHandlers);
294
- src = fixRelativeNewURL(src);
294
+ src = fixRelativeNewURL(src, viteConfig?.base);
295
295
  }
296
296
  catch (err) {
297
297
  needleLog("needle:local-files", "Error in transform: " + getErrMessage(err), "error");
@@ -303,7 +303,7 @@ export function needleMakeFilesLocal(command, _config, userSettings) {
303
303
  },
304
304
  renderChunk(code, chunk) {
305
305
  if (!chunk.fileName?.endsWith(".js")) return null;
306
- const fixed = fixRelativeNewURL(code);
306
+ const fixed = fixRelativeNewURL(code, viteConfig?.base);
307
307
  if (fixed === code) return null;
308
308
  return {
309
309
  code: fixed,
@@ -314,7 +314,7 @@ export function needleMakeFilesLocal(command, _config, userSettings) {
314
314
  for (const output of Object.values(bundle)) {
315
315
  if (output.type !== "chunk") continue;
316
316
  if (!output.fileName?.endsWith(".js")) continue;
317
- const fixed = fixRelativeNewURL(output.code);
317
+ const fixed = fixRelativeNewURL(output.code, viteConfig?.base);
318
318
  if (fixed !== output.code) output.code = fixed;
319
319
  }
320
320
  },
@@ -24,9 +24,11 @@ export function normalizeWebPath(path: string): string;
24
24
  export function ensureTrailingSlash(path: string): string;
25
25
  /**
26
26
  * @param {string} src
27
+ * @param {string} [base] - Vite base path (e.g. "/" or "/app/"). When provided,
28
+ * ext/ paths are made absolute so they resolve correctly under SPA routing.
27
29
  * @returns {string}
28
30
  */
29
- export function fixRelativeNewURL(src: string): string;
31
+ export function fixRelativeNewURL(src: string, base?: string): string;
30
32
  /**
31
33
  * @param {string} src
32
34
  * @returns {string}
@@ -113,25 +113,49 @@ export function ensureTrailingSlash(path) {
113
113
 
114
114
  /**
115
115
  * @param {string} src
116
+ * @param {string} [base] - Vite base path (e.g. "/" or "/app/"). When provided,
117
+ * ext/ paths are made absolute so they resolve correctly under SPA routing.
116
118
  * @returns {string}
117
119
  */
118
- export function fixRelativeNewURL(src) {
120
+ export function fixRelativeNewURL(src, base) {
121
+ /** @param {string} path */
122
+ function makeAbsolute(path) {
123
+ // Strip any leading ./ ../ or / prefix to get the clean ext/... path
124
+ const clean = path.replace(/^(?:\.\.?\/)+/, '').replace(/^\//, '');
125
+ if (base) return base.replace(/\/+$/, '') + '/' + clean;
126
+ return clean;
127
+ }
128
+
119
129
  src = src.replace(
120
130
  /(?<==\s*)(["'])((?:(?:\.{1,2}\/)|\/)?ext\/[^"']*\/)\1/g,
121
131
  (/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
122
- const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
123
- return `new URL(${quote}${runtimePath}${quote}, self.location?.href || ${quote}${quote}).href`;
132
+ const resolved = makeAbsolute(path);
133
+ return `new URL(${quote}${resolved}${quote}, self.location?.href || ${quote}${quote}).href`;
124
134
  }
125
135
  );
126
136
 
127
137
  src = src.replace(
128
138
  /new\s+URL\s*\(\s*(["'`])((?:(?:\.{1,2}\/)|\/)?ext\/[^"'`]+)\1\s*\)/g,
129
139
  (/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
130
- const runtimePath = path.startsWith("/ext/") ? path.substring(1) : path;
131
- return `new URL(${quote}${runtimePath}${quote}, self.location?.href)`;
140
+ const resolved = makeAbsolute(path);
141
+ return `new URL(${quote}${resolved}${quote}, self.location?.href)`;
132
142
  }
133
143
  );
134
144
 
145
+ // Make remaining ext/ string literals absolute (e.g. font paths passed as
146
+ // function arguments like addVariant("normal","normal","ext/fonts/...")).
147
+ // The regexes above already consumed paths inside new URL() calls and
148
+ // assignment-based patterns, so this only catches leftover bare strings.
149
+ if (base) {
150
+ src = src.replace(
151
+ /(["'`])((?:\.{1,2}\/)?ext\/[^"'`]+)\1/g,
152
+ (/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
153
+ const resolved = makeAbsolute(path);
154
+ return `${quote}${resolved}${quote}`;
155
+ }
156
+ );
157
+ }
158
+
135
159
  return src;
136
160
  }
137
161
 
@@ -35,7 +35,7 @@ export class GenerateMeshBVHWorker extends WorkerBase {
35
35
 
36
36
  worker.onerror = e => {
37
37
 
38
- reject(new Error(`[GenerateMeshBVHWorker] ${e.message || "Unknown error. Please check the server console. If you're using vite try adding 'three-mesh-bvh' to 'optimizeDeps.exclude' in your vite.config.js"}`));
38
+ reject(new Error(`[GenerateMeshBVHWorker] ${e.message || "Could not load worker."}`));
39
39
 
40
40
  };
41
41
 
@@ -12,6 +12,7 @@ import { getParam } from "../engine/engine_utils.js";
12
12
  import { NeedleXRSession } from "../engine/engine_xr.js";
13
13
  import { Avatar_POI } from "./avatar/Avatar_Brain_LookAt.js";
14
14
  import { Behaviour, GameObject } from "./Component.js";
15
+ import { EventList } from "./EventList.js";
15
16
  import { UsageMarker } from "./Interactable.js";
16
17
  import { Rigidbody } from "./RigidBody.js";
17
18
  import { SyncedTransform } from "./SyncedTransform.js";
@@ -155,7 +156,19 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
155
156
  @serializable()
156
157
  public showGizmo: boolean = false;
157
158
 
158
- /**
159
+ /** Invoked once when a drag begins (after the minimum drag distance threshold is met). */
160
+ @serializable(EventList)
161
+ dragStarted: EventList = new EventList();
162
+
163
+ /** Invoked every frame while the object is being dragged. */
164
+ @serializable(EventList)
165
+ dragUpdated: EventList = new EventList();
166
+
167
+ /** Invoked once when the last pointer is released and the drag ends. */
168
+ @serializable(EventList)
169
+ dragEnded: EventList = new EventList();
170
+
171
+ /**
159
172
  * Returns the object currently being dragged by this DragControls component, if any.
160
173
  * @returns The object being dragged or null if no object is currently dragged
161
174
  */
@@ -457,6 +470,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
457
470
  if (!object) return;
458
471
 
459
472
  this._isDragging = true;
473
+ this.dragStarted?.invoke();
460
474
 
461
475
  const sync = GameObject.getComponentInChildren(object, SyncedTransform);
462
476
  if (debug) console.log("DRAG START", sync, object);
@@ -497,9 +511,10 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
497
511
  const object = this._targetObject || this.gameObject;
498
512
 
499
513
  InstancingUtil.markDirty(object);
514
+ this.dragUpdated?.invoke();
500
515
  }
501
516
 
502
- /**
517
+ /**
503
518
  * Called when the last pointer has been removed from this object.
504
519
  * Cleans up drag state and applies final velocities to rigidbodies.
505
520
  * @param evt Pointer event data for the last pointer that was lifted
@@ -507,6 +522,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
507
522
  private onLastDragEnd(evt: PointerEventData | null) {
508
523
  if (!this || !this._isDragging) return;
509
524
  this._isDragging = false;
525
+ this.dragEnded?.invoke();
510
526
  for (const rb of this._draggingRigidbodies) {
511
527
  rb.setVelocity(rb.smoothedVelocity.multiplyScalar(this.context.time.deltaTime));
512
528
  }
@@ -758,19 +758,26 @@ export class OrbitControls extends Behaviour implements ICameraController {
758
758
 
759
759
  if (this.targetBounds) {
760
760
  // #region target bounds
761
- const targetVector = this._controls.target;
762
761
  const boundsCenter = this.targetBounds.worldPosition;
763
762
  const boundsHalfSize = getTempVector(this.targetBounds.worldScale).multiplyScalar(0.5);
764
763
  const min = getTempVector(boundsCenter).sub(boundsHalfSize);
765
764
  const max = getTempVector(boundsCenter).add(boundsHalfSize);
766
- const newTarget = getTempVector(this._controls.target).clamp(min, max);
767
- const duration = .1;
768
- if (duration <= 0) targetVector.copy(newTarget);
769
- else targetVector.lerp(newTarget, this.context.time.deltaTime / duration);
765
+
770
766
  if (this._lookTargetLerpActive) {
771
- if (duration <= 0) this._lookTargetEndPosition.copy(newTarget);
772
- else this._lookTargetEndPosition.lerp(newTarget, this.context.time.deltaTime / (duration * 5));
767
+ // During a programmatic transition (fitCamera / setLookTargetPosition with immediate: false),
768
+ // only clamp the destination. The look-target lerp (above) handles moving _controls.target
769
+ // towards the endpoint — we must not fight it by also lerping _controls.target here.
770
+ this._lookTargetEndPosition.clamp(min, max);
771
+ }
772
+ else {
773
+ // Interactive use (pan/orbit): smoothly push the target back into bounds
774
+ const targetVector = this._controls.target;
775
+ const newTarget = getTempVector(targetVector).clamp(min, max);
776
+ const duration = .1;
777
+ if (duration <= 0) targetVector.copy(newTarget);
778
+ else targetVector.lerp(newTarget, Math.min(1, this.context.time.deltaTime / duration));
773
779
  }
780
+
774
781
  if (debug) {
775
782
  Gizmos.DrawWireBox(boundsCenter, boundsHalfSize.multiplyScalar(2), 0xffaa00);
776
783
  }
@@ -847,7 +854,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
847
854
  this._controls.update(this.context.time.deltaTime);
848
855
 
849
856
  if (debug) {
850
- Gizmos.DrawWireSphere(this._controls.target, 0.1, 0x00ff00);
857
+ const distance = this._controls.getDistance();
858
+ Gizmos.DrawWireSphere(this._controls.target, 0.01 * distance, 0x00ff00);
851
859
  }
852
860
  }
853
861
  }
@@ -1022,7 +1030,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
1022
1030
 
1023
1031
  if (debug) {
1024
1032
  console.warn("OrbitControls: setLookTargetPosition", position, immediateOrDuration);
1025
- Gizmos.DrawWireSphere(this._lookTargetEndPosition, .2, 0xff0000, 2);
1033
+ const distance = this._controls.getDistance();
1034
+ Gizmos.DrawWireSphere(this._lookTargetEndPosition, 0.01 * distance, 0xff5500, 2);
1026
1035
  }
1027
1036
 
1028
1037
  if (immediateOrDuration === true) {
@@ -81,7 +81,24 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
81
81
  @serializable()
82
82
  supportRichText: boolean = false;
83
83
  @serializable(URL)
84
- font?: string;
84
+ set font(val: string | null) {
85
+ if (val !== this._font) {
86
+ this._font = val;
87
+ // If the font is assigned during deserialization it means the font URL MUST be resolved with the style suffix. If it's assigned at runtime by a user invocation and is absolute then we assume the user is prividing a full path to the font asset json or png
88
+ this._assignedAtRuntime = this.__didAwake;
89
+ }
90
+ }
91
+ get font(): string | null {
92
+ return this._font;
93
+ }
94
+ private _font: string | null = null;
95
+ private _assignedAtRuntime: boolean = true;
96
+
97
+
98
+ /**
99
+ * Whether to support basic rich text tags in the `text` property. Supported tags include `<b>`, `<i>`, and `<color=hex>`. For example: `Hello <b>World</b>` or `Score: <color=#ff0000>100</color>`
100
+ * @default false
101
+ */
85
102
  @serializable()
86
103
  fontStyle: FontStyle = FontStyle.Normal;
87
104
 
@@ -224,8 +241,15 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
224
241
  // fontSize /= this.canvas?.scaleFactor;
225
242
  // }
226
243
 
227
-
228
- const textOpts = {
244
+ const textOpts: {
245
+ color: Color,
246
+ fontOpacity: number,
247
+ fontSize: number,
248
+ fontKerning: string,
249
+ whiteSpace?: string,
250
+ overflow?: string,
251
+ lineHeight?: number
252
+ } = {
229
253
  color: this.color,
230
254
  fontOpacity: this.color.alpha,
231
255
  fontSize: fontSize,
@@ -486,15 +510,16 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
486
510
  * @private
487
511
  */
488
512
  private setFont(opts: ThreeMeshUIEveryOptions, fontStyle: FontStyle) {
513
+
514
+ if (!this.font) {
515
+ if(debug) console.warn("No font set for Text component, skipping font setup");
516
+ return;
517
+ }
489
518
 
490
- // @TODO : THH could be useful to uniformize font family name :
491
- // This would ease possible html/vr matching
492
- // - Arial instead of assets/arial
493
- // - Arial should stay Arial instead of arial
494
- if (!this.font) return;
495
519
  const fontName = this.font;
496
520
  const familyName = this.getFamilyNameWithCorrectSuffix(fontName, fontStyle);
497
- if (debug) console.log("Selected font family:" + familyName);
521
+
522
+ if (debug) console.log("Selected font family:" + familyName, this.font);
498
523
 
499
524
  // ensure a font family is register under this name
500
525
  let fontFamily = ThreeMeshUI.FontLibrary.getFontFamily(familyName as string);
@@ -527,9 +552,7 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
527
552
  opts.fontWeight = 400;
528
553
  }
529
554
 
530
-
531
555
  // Ensure a fontVariant is registered
532
- //@TODO: @swingingtom add type for fontWeight
533
556
  let fontVariant = fontFamily.getVariant(opts.fontWeight as any as string, opts.fontStyle);
534
557
  if (!fontVariant) {
535
558
  let jsonPath = familyName;
@@ -550,13 +573,7 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
550
573
 
551
574
  private getFamilyNameWithCorrectSuffix(familyName: string, style: FontStyle): string {
552
575
 
553
-
554
- // the URL decorator resolves the URL to absolute URLs - we need to remove the domain part since we're only interested in the path
555
- if (familyName.startsWith("https:") || familyName.startsWith("http:")) {
556
- const url = new URL(familyName);
557
- familyName = url.pathname;
558
- }
559
-
576
+ const isAbsolute = familyName.startsWith("https:") || familyName.startsWith("http:");
560
577
 
561
578
  // we can only change the style for the family if the name has a suffix (e.g. Arial-Bold)
562
579
  const styleSeparator = familyName.lastIndexOf('-');
@@ -580,8 +597,16 @@ export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiv
580
597
  }
581
598
  const isUpperCase = fontBaseName[0] === fontBaseName[0].toUpperCase();
582
599
  const fontNameWithoutSuffix = familyName.substring(0, styleSeparator > pathSeparatorIndex ? styleSeparator : familyName.length);
600
+
583
601
  if (debug) console.log("Select font: ", familyName, FontStyle[style], fontBaseName, isUpperCase, fontNameWithoutSuffix);
584
602
 
603
+ /**
604
+ * If a user provides a font with an absolute URL AND the font name does not end with "-msdf.json" or ".png" (e.g. "https://example.com/fonts/Arial-Bold"), we will assume that the user is providing the full path to the font files and we will not try to modify the font name based on the style. This allows users to have more control over the font files they are using, especially if they are hosting their own fonts or using a custom font provider that does not follow the same naming conventions as our default fonts.
605
+ */
606
+ if (isAbsolute && this._assignedAtRuntime && !(familyName.endsWith("-msdf.json") || familyName.endsWith(".png"))) {
607
+ return fontNameWithoutSuffix;
608
+ }
609
+
585
610
  switch (style) {
586
611
  case FontStyle.Normal:
587
612
  if (isUpperCase) return fontNameWithoutSuffix + "-Regular";
@@ -289,20 +289,28 @@ export class CursorFollow extends Behaviour {
289
289
 
290
290
 
291
291
  if (this.snapToSurface) {
292
- ray.origin = _position;
293
- ray.direction = rayDirection.multiplyScalar(-1);
294
- const hits = this.context.physics.raycastFromRay(ray);
295
- if (hits?.length) {
296
- const hit = hits[0];
297
- if (this.damping > 0) {
298
- this.gameObject.worldPosition = _position.lerp(hit.point, this.context.time.deltaTime / this.damping);
299
- }
300
- else {
301
- this.gameObject.worldPosition = hit.point;
292
+ ray.origin = cameraPosition;
293
+ ray.direction = rayDirection;
294
+ const hits = this.context.physics.raycastFromRay(ray, {
295
+ testObject: obj => {
296
+ return obj !== this.gameObject && !this.gameObject.contains(obj) ? true : false
302
297
  }
303
-
304
- if(debug) {
305
- Gizmos.DrawLine(hit.point, hit.normal!.add(hit.point), 0x00FF00);
298
+ });
299
+ if (hits?.length) {
300
+ // Get the first hit that is not a child of *this* object. Because we do not want to lerp to a surface that is part of the object itself, since that would result in *this* object moving closer and closer to the camera as it tries to snap to itself
301
+ const hit = hits[0];//.find(h => !this.gameObject.contains(h.object));
302
+ if (hit) {
303
+ if (this.damping > 0) {
304
+ this.gameObject.worldPosition = _position.lerp(hit.point, this.context.time.deltaTime / this.damping);
305
+ // this._distance = this.gameObject.worldPosition.distanceTo(cameraPosition);
306
+ }
307
+ else {
308
+ this.gameObject.worldPosition = hit.point;
309
+ // this._distance = hit.point.distanceTo(cameraPosition);
310
+ }
311
+ if (debug) {
312
+ Gizmos.DrawLine(hit.point, hit.normal!.add(hit.point), 0x00FF00);
313
+ }
306
314
  }
307
315
  }
308
316
  }