@needle-tools/engine 3.0.1-alpha.5 → 3.1.0-alpha

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 (100) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/needle-engine.js +24245 -24524
  3. package/dist/needle-engine.min.js +367 -354
  4. package/dist/needle-engine.umd.cjs +366 -353
  5. package/lib/engine/api.d.ts +1 -1
  6. package/lib/engine/api.js +1 -1
  7. package/lib/engine/api.js.map +1 -1
  8. package/lib/engine/codegen/register_types.js +8 -0
  9. package/lib/engine/codegen/register_types.js.map +1 -1
  10. package/lib/engine/engine_addressables.d.ts +21 -1
  11. package/lib/engine/engine_addressables.js +83 -7
  12. package/lib/engine/engine_addressables.js.map +1 -1
  13. package/lib/engine/engine_serialization.d.ts +2 -2
  14. package/lib/engine/engine_serialization.js +2 -3
  15. package/lib/engine/engine_serialization.js.map +1 -1
  16. package/lib/engine/engine_serialization_builtin_serializer.d.ts +5 -0
  17. package/lib/engine/engine_serialization_builtin_serializer.js +16 -0
  18. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  19. package/lib/engine/engine_serialization_core.d.ts +1 -0
  20. package/lib/engine/engine_serialization_core.js +26 -20
  21. package/lib/engine/engine_serialization_core.js.map +1 -1
  22. package/lib/engine/engine_utils.d.ts +9 -0
  23. package/lib/engine/engine_utils.js +33 -13
  24. package/lib/engine/engine_utils.js.map +1 -1
  25. package/lib/engine/extensions/NEEDLE_animator_controller_model.d.ts +1 -0
  26. package/lib/engine/extensions/NEEDLE_animator_controller_model.js.map +1 -1
  27. package/lib/engine/extensions/NEEDLE_progressive.js +2 -2
  28. package/lib/engine/extensions/NEEDLE_progressive.js.map +1 -1
  29. package/lib/engine-components/AnimatorController.js +22 -3
  30. package/lib/engine-components/AnimatorController.js.map +1 -1
  31. package/lib/engine-components/AudioSource.js +3 -2
  32. package/lib/engine-components/AudioSource.js.map +1 -1
  33. package/lib/engine-components/Component.js.map +1 -1
  34. package/lib/engine-components/ParticleSystem.d.ts +5 -2
  35. package/lib/engine-components/ParticleSystem.js +34 -4
  36. package/lib/engine-components/ParticleSystem.js.map +1 -1
  37. package/lib/engine-components/ParticleSystemModules.d.ts +1 -0
  38. package/lib/engine-components/ParticleSystemModules.js +1 -1
  39. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  40. package/lib/engine-components/Skybox.js +2 -2
  41. package/lib/engine-components/Skybox.js.map +1 -1
  42. package/lib/engine-components/VideoPlayer.d.ts +1 -2
  43. package/lib/engine-components/VideoPlayer.js +6 -7
  44. package/lib/engine-components/VideoPlayer.js.map +1 -1
  45. package/lib/engine-components/WebXR.d.ts +4 -1
  46. package/lib/engine-components/WebXR.js +10 -2
  47. package/lib/engine-components/WebXR.js.map +1 -1
  48. package/lib/engine-components/WebXRController.js +2 -2
  49. package/lib/engine-components/WebXRController.js.map +1 -1
  50. package/lib/engine-components/WebXRImageTracking.d.ts +39 -0
  51. package/lib/engine-components/WebXRImageTracking.js +173 -0
  52. package/lib/engine-components/WebXRImageTracking.js.map +1 -0
  53. package/lib/engine-components/codegen/components.d.ts +4 -0
  54. package/lib/engine-components/codegen/components.js +4 -0
  55. package/lib/engine-components/codegen/components.js.map +1 -1
  56. package/lib/engine-components/postprocessing/Effects/DepthOfField.d.ts +1 -0
  57. package/lib/engine-components/postprocessing/Effects/DepthOfField.js +4 -0
  58. package/lib/engine-components/postprocessing/Effects/DepthOfField.js.map +1 -1
  59. package/lib/engine-components/postprocessing/Effects/TiltShiftEffect.d.ts +13 -0
  60. package/lib/engine-components/postprocessing/Effects/TiltShiftEffect.js +63 -0
  61. package/lib/engine-components/postprocessing/Effects/TiltShiftEffect.js.map +1 -0
  62. package/lib/engine-components/timeline/TimelineTracks.js +2 -2
  63. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  64. package/lib/engine-components/ui/Text.js +7 -7
  65. package/lib/engine-components/ui/Text.js.map +1 -1
  66. package/lib/include/three/ARButton.d.ts +1 -1
  67. package/lib/include/three/ARButton.js +2 -1
  68. package/lib/include/three/ARButton.js.map +1 -1
  69. package/lib/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +1 -1
  71. package/plugins/vite/config.js +8 -0
  72. package/plugins/vite/copyfiles.js +12 -3
  73. package/plugins/vite/drop.js +1 -0
  74. package/plugins/vite/index.js +2 -0
  75. package/plugins/vite/transform-codegen.js +45 -0
  76. package/src/engine/api.ts +1 -1
  77. package/src/engine/codegen/register_types.js +8 -0
  78. package/src/engine/engine_addressables.ts +102 -8
  79. package/src/engine/engine_serialization.ts +5 -4
  80. package/src/engine/engine_serialization_builtin_serializer.ts +23 -3
  81. package/src/engine/engine_serialization_core.ts +27 -20
  82. package/src/engine/engine_utils.ts +35 -14
  83. package/src/engine/extensions/NEEDLE_animator_controller_model.ts +1 -0
  84. package/src/engine/extensions/NEEDLE_progressive.ts +2 -2
  85. package/src/engine-components/AnimatorController.ts +18 -3
  86. package/src/engine-components/AudioSource.ts +2 -2
  87. package/src/engine-components/Component.ts +1 -1
  88. package/src/engine-components/ParticleSystem.ts +34 -4
  89. package/src/engine-components/ParticleSystemModules.ts +1 -1
  90. package/src/engine-components/Skybox.ts +2 -2
  91. package/src/engine-components/VideoPlayer.ts +6 -6
  92. package/src/engine-components/WebXR.ts +11 -2
  93. package/src/engine-components/WebXRController.ts +2 -2
  94. package/src/engine-components/WebXRImageTracking.ts +192 -0
  95. package/src/engine-components/codegen/components.ts +4 -0
  96. package/src/engine-components/postprocessing/Effects/DepthOfField.ts +3 -0
  97. package/src/engine-components/postprocessing/Effects/TiltShiftEffect.ts +56 -0
  98. package/src/engine-components/timeline/TimelineTracks.ts +2 -2
  99. package/src/engine-components/ui/Text.ts +7 -7
  100. package/src/include/three/ARButton.js +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.0.1-alpha.5",
3
+ "version": "3.1.0-alpha",
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.umd.cjs",
6
6
  "type": "module",
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from 'fs';
2
2
 
3
+ /** the codegen meta file */
3
4
  export async function loadConfig(path) {
4
5
  try {
5
6
  // First try to get the path from the config
@@ -28,6 +29,7 @@ export async function loadConfig(path) {
28
29
  }
29
30
  }
30
31
 
32
+ /** get the needle.config.json */
31
33
  export function tryLoadProjectConfig() {
32
34
  try {
33
35
  const root = process.cwd();
@@ -45,4 +47,10 @@ export function tryLoadProjectConfig() {
45
47
  }
46
48
 
47
49
  return null;
50
+ }
51
+
52
+
53
+ /** "assets" -> the directory name inside the output directory to put e.g. glb files into */
54
+ export function builtAssetsDirectory(){
55
+ return "assets";
48
56
  }
@@ -1,6 +1,7 @@
1
1
 
2
2
  import { resolve, join } from 'path'
3
3
  import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
4
+ import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
4
5
 
5
6
 
6
7
  /** copy files on build from assets to dist */
@@ -19,7 +20,14 @@ export const needleCopyFiles = (command, config, userSettings) => {
19
20
  const baseDir = process.cwd();
20
21
  const pluginName = "needle-copy-files";
21
22
 
22
- const outdirName = "dist";
23
+ let assetsDirName = "assets";
24
+ let outdirName = "dist";
25
+
26
+ const needleConfig = tryLoadProjectConfig();
27
+ if(needleConfig){
28
+ assetsDirName = needleConfig.assetsDirectory;
29
+ }
30
+
23
31
  const outDir = resolve(baseDir, outdirName);
24
32
  if (!existsSync(outDir)) {
25
33
  mkdirSync(outDir);
@@ -36,9 +44,9 @@ export const needleCopyFiles = (command, config, userSettings) => {
36
44
  }
37
45
 
38
46
  // copy assets dir
39
- const assetsDir = resolve(baseDir, 'assets');
47
+ const assetsDir = resolve(baseDir, assetsDirName);
40
48
  if (existsSync(assetsDir)) {
41
- console.log(`[${pluginName}] - Copy assets to ${outdirName}/assets`)
49
+ console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
42
50
  const targetDir = resolve(outDir, 'assets');
43
51
  copyRecursiveSync(assetsDir, targetDir);
44
52
  }
@@ -53,6 +61,7 @@ export const needleCopyFiles = (command, config, userSettings) => {
53
61
  }
54
62
  }
55
63
 
64
+
56
65
  function copyRecursiveSync(src, dest) {
57
66
  var exists = existsSync(src);
58
67
  var stats = exists && statSync(src);
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = path.dirname(__filename);
8
8
 
9
+ /** experimental, allow dropping files from Unity into the running scene */
9
10
  export const needleDrop = (command, config, userSettings) => {
10
11
  if (command === "build") return;
11
12
 
@@ -6,6 +6,7 @@ import { needleDrop } from "./drop.js";
6
6
  import { editorConnection } from "./editor-connection.js";
7
7
  import { needleCopyFiles } from "./copyfiles.js";
8
8
  import { needleViteAlias } from "./alias.js";
9
+ import { needleTransformCodegen } from "./transform-codegen.js";
9
10
  import { needleLicense } from "./license.js";
10
11
 
11
12
  export * from "./gzip.js";
@@ -27,6 +28,7 @@ export const needlePlugins = async (command, config, userSettings) => {
27
28
  needleReload(command, config, userSettings),
28
29
  needleBuild(command, config, userSettings),
29
30
  needleCopyFiles(command, config, userSettings),
31
+ needleTransformCodegen(command, config, userSettings),
30
32
  needleDrop(command, config, userSettings),
31
33
  ];
32
34
  array.push(await editorConnection(command, config, userSettings, array));
@@ -0,0 +1,45 @@
1
+ import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
2
+
3
+ /**
4
+ * modify the glb load path in codegen files
5
+ * this is necessary if the assets directory is not the default (changed by the user in needle.config.json)
6
+ */
7
+ export const needleTransformCodegen = (command, config, userSettings) => {
8
+
9
+ if (config?.noCodegenTransform === true || userSettings?.noCodegenTransform === true) {
10
+ return;
11
+ }
12
+
13
+ let codegenDirectory = "src/generated";
14
+ const needleConfig = tryLoadProjectConfig();
15
+ if (needleConfig?.codegenDirectory?.length)
16
+ codegenDirectory = needleConfig.codegenDirectory;
17
+
18
+ let configuredAssetsDirectory = "assets";
19
+ if (needleConfig?.assetsDirectory?.length)
20
+ configuredAssetsDirectory = needleConfig.assetsDirectory;
21
+
22
+ // https://regex101.com/r/Y05z9P/1
23
+ // const matchCodegenFilePaths = /\"(.+)\/.+?\.(glb|gltf)/g;
24
+
25
+ return [
26
+ {
27
+ name: 'needle-transform-files',
28
+ apply: 'build',
29
+ transform(src, id) {
30
+ if (id.endsWith(codegenDirectory + "/gen.js")) {
31
+ const assetsDir = builtAssetsDirectory();
32
+ if (assetsDir !== configuredAssetsDirectory) {
33
+ console.log(`[needle-transform-files] - Transform codegen paths \"${configuredAssetsDirectory}\" → \"${assetsDir}\"`)
34
+ // replace codegen paths
35
+ src = src.replaceAll(configuredAssetsDirectory, assetsDir);
36
+ return {
37
+ code: src,
38
+ map: null
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ];
45
+ }
package/src/engine/api.ts CHANGED
@@ -6,7 +6,7 @@ export { InstancingUtil } from "./engine_instancing";
6
6
  export * from "./engine_gameobject";
7
7
  export * from "./engine_components";
8
8
  export * from "./engine_components_internal";
9
- export { AssetReference } from "./engine_addressables";
9
+ export { AssetReference, ImageReference } from "./engine_addressables";
10
10
  export { Context, FrameEvent } from "./engine_setup";
11
11
  export * from "./debug/debug";
12
12
  export { validate } from "./engine_util_decorator"
@@ -150,6 +150,7 @@ import { TestRunner } from "../../engine-components/TestRunner";
150
150
  import { TestSimulateUserData } from "../../engine-components/TestRunner";
151
151
  import { Text } from "../../engine-components/ui/Text";
152
152
  import { TextureSheetAnimationModule } from "../../engine-components/ParticleSystemModules";
153
+ import { TiltShiftEffect } from "../../engine-components/postprocessing/Effects/TiltShiftEffect";
153
154
  import { ToneMapping } from "../../engine-components/postprocessing/Effects/Tonemapping";
154
155
  import { TrailModule } from "../../engine-components/ParticleSystemModules";
155
156
  import { TransformData } from "../../engine-components/export/usdz/extensions/Animation";
@@ -173,7 +174,10 @@ import { WebARSessionRoot } from "../../engine-components/WebARSessionRoot";
173
174
  import { WebXR } from "../../engine-components/WebXR";
174
175
  import { WebXRAvatar } from "../../engine-components/WebXRAvatar";
175
176
  import { WebXRController } from "../../engine-components/WebXRController";
177
+ import { WebXRImageTracking } from "../../engine-components/WebXRImageTracking";
178
+ import { WebXRImageTrackingModel } from "../../engine-components/WebXRImageTracking";
176
179
  import { WebXRSync } from "../../engine-components/WebXRSync";
180
+ import { WebXRTrackedImage } from "../../engine-components/WebXRImageTracking";
177
181
  import { XRFlag } from "../../engine-components/XRFlag";
178
182
  import { XRGrabModel } from "../../engine-components/WebXRGrabRendering";
179
183
  import { XRGrabRendering } from "../../engine-components/WebXRGrabRendering";
@@ -330,6 +334,7 @@ TypeStore.add("TestRunner", TestRunner);
330
334
  TypeStore.add("TestSimulateUserData", TestSimulateUserData);
331
335
  TypeStore.add("Text", Text);
332
336
  TypeStore.add("TextureSheetAnimationModule", TextureSheetAnimationModule);
337
+ TypeStore.add("TiltShiftEffect", TiltShiftEffect);
333
338
  TypeStore.add("ToneMapping", ToneMapping);
334
339
  TypeStore.add("TrailModule", TrailModule);
335
340
  TypeStore.add("TransformData", TransformData);
@@ -353,7 +358,10 @@ TypeStore.add("WebARSessionRoot", WebARSessionRoot);
353
358
  TypeStore.add("WebXR", WebXR);
354
359
  TypeStore.add("WebXRAvatar", WebXRAvatar);
355
360
  TypeStore.add("WebXRController", WebXRController);
361
+ TypeStore.add("WebXRImageTracking", WebXRImageTracking);
362
+ TypeStore.add("WebXRImageTrackingModel", WebXRImageTrackingModel);
356
363
  TypeStore.add("WebXRSync", WebXRSync);
364
+ TypeStore.add("WebXRTrackedImage", WebXRTrackedImage);
357
365
  TypeStore.add("XRFlag", XRFlag);
358
366
  TypeStore.add("XRGrabModel", XRGrabModel);
359
367
  TypeStore.add("XRGrabRendering", XRGrabRendering);
@@ -1,8 +1,8 @@
1
- import { getParam, getPath } from "../engine/engine_utils";
1
+ import { getParam, resolveUrl } from "../engine/engine_utils";
2
2
  // import { loadSync, parseSync } from "./engine_scenetools";
3
3
  import { SerializationContext, TypeSerializer } from "./engine_serialization_core";
4
4
  import { Context } from "./engine_setup";
5
- import { Group, Object3D, Scene } from "three";
5
+ import { Group, Object3D, Scene, Texture } from "three";
6
6
  import { processNewScripts } from "./engine_mainloop_utils";
7
7
  import { registerPrefabProvider, syncInstantiate } from "./engine_networking_instantiate";
8
8
  import { download, hash } from "./engine_web_api";
@@ -50,7 +50,7 @@ export type ProgressCallback = (asset: AssetReference, prog: ProgressEvent) => v
50
50
  export class AssetReference {
51
51
 
52
52
  static getOrCreate(sourceId: SourceIdentifier, uri: string, context: Context): AssetReference {
53
- const fullPath = getPath(sourceId, uri);
53
+ const fullPath = resolveUrl(sourceId, uri);
54
54
  if (debug) console.log("GetOrCreate Addressable from", sourceId, uri, "FinalPath=", fullPath);
55
55
  const addressables = context.addressables;
56
56
  const existing = addressables.findAssetReference(fullPath);
@@ -72,15 +72,16 @@ export class AssetReference {
72
72
 
73
73
  private _loading?: PromiseLike<any>;
74
74
 
75
+ // TODO: rename to url
75
76
  get uri(): string {
76
- return this._uri;
77
+ return this._url;
77
78
  }
78
79
 
79
80
  get rawAsset(): any { return this._asset; }
80
81
 
81
82
  private _asset: any;
82
83
  private _glbRoot?: Object3D | null;
83
- private _uri: string;
84
+ private _url: string;
84
85
  private _progressListeners: ProgressCallback[] = [];
85
86
 
86
87
  private _hash?: string;
@@ -90,14 +91,14 @@ export class AssetReference {
90
91
  private _rawBinary?: ArrayBuffer | null;
91
92
 
92
93
  constructor(uri: string, hash?: string) {
93
- this._uri = uri;
94
+ this._url = uri;
94
95
  this._hash = hash;
95
96
  if (uri.includes("?v="))
96
97
  this._hashedUri = uri;
97
98
  else
98
99
  this._hashedUri = hash ? uri + "?v=" + hash : uri;
99
100
 
100
- registerPrefabProvider(this._uri, this.onResolvePrefab.bind(this));
101
+ registerPrefabProvider(this._url, this.onResolvePrefab.bind(this));
101
102
  }
102
103
 
103
104
  private async onResolvePrefab(uri: string): Promise<IGameObject | null> {
@@ -330,4 +331,97 @@ class AddressableSerializer extends TypeSerializer {
330
331
  }
331
332
 
332
333
  }
333
- new AddressableSerializer();
334
+ new AddressableSerializer();
335
+
336
+
337
+
338
+
339
+
340
+
341
+ export class ImageReference {
342
+
343
+ private static imageReferences = new Map<string, ImageReference>();
344
+
345
+ static getOrCreate(url: string) {
346
+ let ref = ImageReference.imageReferences.get(url);
347
+ if (!ref) {
348
+ ref = new ImageReference(url);
349
+ ImageReference.imageReferences.set(url, ref);
350
+ }
351
+ return ref;
352
+ }
353
+
354
+ constructor(url: string) {
355
+ this.url = url;
356
+ }
357
+
358
+ readonly url!: string;
359
+
360
+ private _bitmap?: Promise<ImageBitmap | null>;
361
+ private _bitmapObject?: ImageBitmap;
362
+
363
+ dispose() {
364
+ if (this._bitmapObject) {
365
+ this._bitmapObject.close();
366
+ }
367
+ this._bitmap = undefined;
368
+ }
369
+
370
+ createHTMLImage(): HTMLImageElement {
371
+ const img = new Image();
372
+ img.src = this.url;
373
+ return img;
374
+ }
375
+
376
+ createTexture(): Promise<Texture | null> {
377
+ return this.getBitmap().then((bitmap) => {
378
+ if (bitmap) {
379
+ const texture = new Texture(bitmap);
380
+ texture.needsUpdate = true;
381
+ return texture;
382
+ }
383
+ return null;
384
+ });
385
+ }
386
+
387
+ /** Loads the bitmap data of the image */
388
+ getBitmap(): Promise<ImageBitmap | null> {
389
+ if (this._bitmap) return this._bitmap;
390
+ this._bitmap = new Promise((res, _) => {
391
+ const imageElement = document.createElement("img") as HTMLImageElement;
392
+ imageElement.addEventListener("load", () => {
393
+ this._bitmap = createImageBitmap(imageElement).then((bitmap) => {
394
+ this._bitmapObject = bitmap;
395
+ res(bitmap);
396
+ return bitmap;
397
+ })
398
+ });
399
+ imageElement.addEventListener("error", err => {
400
+ console.error("Failed to load image:" + this.url, err);
401
+ res(null);
402
+ });
403
+ imageElement.src = this.url;
404
+ });
405
+ return this._bitmap;
406
+ }
407
+ }
408
+
409
+
410
+ export class ImageReferenceSerializer extends TypeSerializer {
411
+ constructor() {
412
+ super([ImageReference]);
413
+ }
414
+
415
+ onSerialize(_data: string, _context: SerializationContext) {
416
+ return null;
417
+ }
418
+
419
+ onDeserialize(data: string, _context: SerializationContext) {
420
+ if (typeof data === "string") {
421
+ const url = resolveUrl(_context.gltfId, data)
422
+ return ImageReference.getOrCreate(url);
423
+ }
424
+ return undefined;
425
+ }
426
+ }
427
+ new ImageReferenceSerializer();
@@ -1,6 +1,7 @@
1
1
  import { serializeObject, deserializeObject } from "./engine_serialization_core";
2
- import * as builtin from "./engine_serialization_builtin_serializer";
3
- // export builtin so it will be called and registered
4
- export { serializeObject, deserializeObject, builtin };
5
2
 
6
- export { serializable, serializeable } from "./engine_serialization_decorator"
3
+ export { serializeObject, deserializeObject };
4
+
5
+ export { serializable, serializeable } from "./engine_serialization_decorator"
6
+
7
+ export * from "./engine_serialization_builtin_serializer";
@@ -7,6 +7,7 @@ import { CallInfo, EventList } from "../engine-components/EventList";
7
7
  import { Color, Object3D, Texture, WebGLRenderTarget } from "three";
8
8
  import { RenderTexture } from "./engine_texture";
9
9
  import { isDevEnvironment } from "../engine/debug/debug";
10
+ import { resolveUrl } from "./engine_utils";
10
11
 
11
12
  // export class SourcePath {
12
13
  // src?:string
@@ -300,8 +301,8 @@ class EventListSerializer extends TypeSerializer {
300
301
  return undefined;
301
302
  }
302
303
 
303
- private createEventMethod(target : object, methodName: string, args?: any) : Function | undefined {
304
-
304
+ private createEventMethod(target: object, methodName: string, args?: any): Function | undefined {
305
+
305
306
  return (...forwardedArgs) => {
306
307
  const method = target[methodName];
307
308
  if (typeof method === "function") {
@@ -339,4 +340,23 @@ export class RenderTextureSerializer extends TypeSerializer {
339
340
  return undefined;
340
341
  }
341
342
  }
342
- new RenderTextureSerializer();
343
+ new RenderTextureSerializer();
344
+
345
+
346
+ export class UriSerializer extends TypeSerializer {
347
+ constructor() {
348
+ super([URL]);
349
+ }
350
+
351
+ onSerialize(_data: string, _context: SerializationContext) {
352
+ return null;
353
+ }
354
+
355
+ onDeserialize(data: string, _context: SerializationContext) {
356
+ if (typeof data === "string") {
357
+ return resolveUrl(_context.gltfId, data);
358
+ }
359
+ return undefined;
360
+ }
361
+ }
362
+ new UriSerializer();
@@ -173,6 +173,7 @@ export class SerializationContext {
173
173
  root: THREE.Object3D;
174
174
 
175
175
  gltf?: GLTF;
176
+ /** the url of the glb that is currently being loaded */
176
177
  gltfId?: SourceIdentifier;
177
178
  object!: THREE.Object3D;
178
179
  target?: object;
@@ -290,16 +291,21 @@ export function deserializeObject(obj: ISerializable, serializedData: object, co
290
291
  context.type = undefined;
291
292
  context.path = key;
292
293
 
294
+ if (obj.onBeforeDeserializeMember !== undefined) {
295
+ // callback to the instance, if it returns true assume it's done all the things itself
296
+ if (obj.onBeforeDeserializeMember(key, data, context) === true) continue;
297
+ }
298
+
293
299
  if (serializedEntryInfo === null) {
294
300
  obj[key] = data;
301
+ // if(typeof data === "string"){
302
+ // const serializer = helper.getSerializerForConstructor(String);
303
+ // const res = serializer?.onDeserialize(data, context);
304
+ // if(res !== undefined) obj[key] = res;
305
+ // }
295
306
  }
296
307
  else {
297
308
 
298
- if (obj.onBeforeDeserializeMember !== undefined) {
299
- // callback to the instance, if it returns true assume it's done all the things itself
300
- if (obj.onBeforeDeserializeMember(key, data, context) === true) continue;
301
- }
302
-
303
309
  if (Array.isArray(serializedEntryInfo)) {
304
310
  for (let i = 0; i < serializedEntryInfo.length; i++) {
305
311
  const typeInfoOrConstructor = serializedEntryInfo[i];
@@ -314,24 +320,25 @@ export function deserializeObject(obj: ISerializable, serializedData: object, co
314
320
  obj[key] = tryResolve(serializedEntryInfo);
315
321
  }
316
322
 
317
- function tryResolve(typeInfoOrConstructor) {
318
- const typeInformationOrConstructor = typeInfoOrConstructor as ITypeInformation;
319
- // if the entry does specify an object of type ITypeInformation and has the type field set
320
- const type = typeInformationOrConstructor.type;
321
- if (type) {
322
- return deserializeObjectWithType(data, type, context, undefined, obj[key]);
323
- }
324
- // it can also just contain a constructor
325
- else {
326
- const constructor = typeInfoOrConstructor as Constructor<any>;
327
- return deserializeObjectWithType(data, constructor, context, undefined, obj[key]);
328
- }
329
- }
330
323
 
331
324
  buffer.length = 0;
325
+ }
326
+
327
+ if (obj.onAfterDeserializeMember !== undefined) {
328
+ obj.onAfterDeserializeMember(key, data, context);
329
+ }
332
330
 
333
- if (obj.onAfterDeserializeMember !== undefined) {
334
- obj.onAfterDeserializeMember(key, data, context);
331
+ function tryResolve(typeInfoOrConstructor) {
332
+ const typeInformationOrConstructor = typeInfoOrConstructor as ITypeInformation;
333
+ // if the entry does specify an object of type ITypeInformation and has the type field set
334
+ const type = typeInformationOrConstructor.type;
335
+ if (type) {
336
+ return deserializeObjectWithType(data, type, context, undefined, obj[key]);
337
+ }
338
+ // it can also just contain a constructor
339
+ else {
340
+ const constructor = typeInfoOrConstructor as Constructor<any>;
341
+ return deserializeObjectWithType(data, constructor, context, undefined, obj[key]);
335
342
  }
336
343
  }
337
344
  }
@@ -203,30 +203,51 @@ export function delay(milliseconds: number): Promise<void> {
203
203
  });
204
204
  }
205
205
 
206
- // if a timeline is exported via menu item the audio clip path is relative to the glb (same folder)
206
+ // 1) if a timeline is exported via menu item the audio clip path is relative to the glb (same folder)
207
207
  // we need to detect that here and build the new audio source path relative to the new glb location
208
208
  // the same is/might be true for any file that is/will be exported via menu item
209
- const debugGetPath = getParam("debugsourcepath");
210
- export function getPath(source: SourceIdentifier | undefined, uri: string): string {
211
- if (source === undefined) {
212
- if (debugGetPath) console.warn("getPath: source is undefined, returning uri", uri);
209
+ // 2) if the needle.config assetDirectory is modified (from e.g. /assets to /needle/assets) when building a distributable our vite transform and copy plugin will move the files to dist/assets hence we cannot use project-relative paths (because the path changes). What we do instead if make all paths serialized in a glb relative to the glb. The rel: prefix is used to detect urls that need to be resolved.
210
+ const debugGetPath = getParam("debugresolveurl");
211
+
212
+ export const relativePathPrefix = "rel:";
213
+
214
+ /** @deprecated use resolveUrl instead */
215
+ export function getPath(source:SourceIdentifier|undefined, uri:string) : string {
216
+ return resolveUrl(source, uri);
217
+ }
218
+ /**
219
+ * Use to resolve a url serialized in a glTF file
220
+ * @param source The uri of the loading file
221
+ * @param uri The uri of the file to resolve, can be absolute or relative
222
+ * @returns The resolved uri
223
+ */
224
+ export function resolveUrl(source: SourceIdentifier | undefined, uri: string): string {
225
+ if (uri === undefined) {
226
+ if (debugGetPath) console.warn("getPath: uri is undefined, returning uri", uri);
227
+ return uri;
228
+ }
229
+ if(uri.startsWith("./")) {
213
230
  return uri;
214
231
  }
215
232
  if (uri.startsWith("http")) {
216
233
  if (debugGetPath) console.warn("getPath: uri is absolute, returning uri", uri);
217
234
  return uri;
218
235
  }
236
+ if (source === undefined) {
237
+ if (debugGetPath) console.warn("getPath: source is undefined, returning uri", uri);
238
+ return uri;
239
+ }
240
+ if(uri.startsWith(relativePathPrefix)){
241
+ uri = uri.substring(4);
242
+ }
219
243
  const pathIndex = source.lastIndexOf("/");
220
244
  if (pathIndex >= 0) {
221
- let newUri = source.substring(0, pathIndex + 1);
222
-
223
- const uriDirectoryIndex = uri.lastIndexOf("/");
224
- if (uriDirectoryIndex >= 0) {
225
- newUri += uri.substring(uriDirectoryIndex + 1);
226
- } else {
227
- newUri += uri;
228
- }
229
- if (debugGetPath) console.log("getPath:", source, " - changed uri from\n", uri, "\n→ ", newUri);
245
+ // Take the source uri as the base path
246
+ const basePath = source.substring(0, pathIndex + 1);
247
+ // Append the relative uri
248
+ let newUri = basePath + uri;
249
+ // newUri = new URL(newUri, globalThis.location.href).href;
250
+ if (debugGetPath) console.log("source:", source, "- changed uri \nfrom", uri, "\n→ ", newUri, "\n" + basePath);
230
251
  return newUri;
231
252
  }
232
253
  return uri;
@@ -32,6 +32,7 @@ export declare type StateMachine = {
32
32
  export declare type State = {
33
33
  name: string,
34
34
  hash: number;
35
+ speed?: number;
35
36
  motion: Motion,
36
37
  transitions: Transition[],
37
38
  behaviours: StateMachineBehaviourModel[],
@@ -3,7 +3,7 @@ import { GLTF, GLTFLoader, GLTFLoaderPlugin, GLTFParser } from "three/examples/j
3
3
  import { SourceIdentifier } from "../engine_types";
4
4
  import { Context } from "../engine_setup";
5
5
  import { addDracoAndKTX2Loaders } from "../engine_loaders";
6
- import { delay, getParam, getPath } from "../engine_utils";
6
+ import { delay, getParam, resolveUrl } from "../engine_utils";
7
7
 
8
8
  export const EXTENSION_NAME = "NEEDLE_progressive";
9
9
 
@@ -130,7 +130,7 @@ export class NEEDLE_progressive implements GLTFLoaderPlugin {
130
130
  if (progressiveInfo) {
131
131
  if (debug)
132
132
  console.log(key, progressiveInfo.uri, progressiveInfo.guid);
133
- const uri = getPath(source, progressiveInfo.uri);
133
+ const uri = resolveUrl(source, progressiveInfo.uri);
134
134
  if (uri.endsWith(".glb") || uri.endsWith(".gltf")) {
135
135
  if (!progressiveInfo.guid) {
136
136
  console.warn("missing pointer for glb/gltf texture", progressiveInfo);
@@ -249,9 +249,16 @@ export class AnimatorController {
249
249
 
250
250
  if (action) {
251
251
  const dur = state.motion.clip!.duration;
252
- const normalizedTime = dur <= 0 ? 1 : action.time / dur;
253
- const makeTransition = transition.hasExitTime ? normalizedTime >= transition.exitTime : true;
254
- // console.log(state.name, makeTransition, transition.hasExitTime, normalizedTime, transition.exitTime)
252
+ const normalizedTime = dur <= 0 ? 1 : Math.abs(action.time / dur);
253
+ let makeTransition = false;
254
+ if (transition.hasExitTime) {
255
+ if (action.timeScale > 0) makeTransition = normalizedTime >= transition.exitTime;
256
+ // When the animation is playing backwards we need to check exit time inverted
257
+ else if(action.timeScale < 0) makeTransition = 1 - normalizedTime >= transition.exitTime;
258
+ }
259
+ else {
260
+ makeTransition = true;
261
+ }
255
262
  if (makeTransition) {
256
263
  // if (transition.hasExitTime && transition.exitTime >= .9999)
257
264
  action.clampWhenFinished = true;
@@ -283,6 +290,12 @@ export class AnimatorController {
283
290
  action.time = 0;
284
291
  action.play();
285
292
  }
293
+ else if (action.time <= 0 && action.timeScale < 0) {
294
+ didTriggerLooping = true;
295
+ action.reset();
296
+ action.time = action.getClip().duration;
297
+ action.play();
298
+ }
286
299
  }
287
300
 
288
301
  // call update state behaviours:
@@ -367,9 +380,11 @@ export class AnimatorController {
367
380
  action.stop();
368
381
  action.reset();
369
382
  action.timeScale = this._speed;
383
+ if (state.speed !== undefined) action.timeScale *= state.speed;
370
384
  action.enabled = true;
371
385
  const duration = state.motion.clip!.duration;
372
386
  action.time = offsetNormalized * duration;
387
+ if(action.timeScale < 0) action.time = duration - action.time;
373
388
  action.clampWhenFinished = true;
374
389
  action.setLoop(LoopOnce, 0);
375
390
  if (durationInSec > 0)
@@ -82,7 +82,7 @@ export class AudioSource extends Behaviour {
82
82
  document.addEventListener('touchstart', fn);
83
83
  }
84
84
 
85
- @serializable()
85
+ @serializable(URL)
86
86
  clip: string = "";
87
87
 
88
88
  @serializable()
@@ -202,7 +202,7 @@ export class AudioSource extends Behaviour {
202
202
  }
203
203
  break;
204
204
  case "visible":
205
- console.log("visible", this.enabled, this.playOnAwake, !this.isPlaying, AudioSource._userInteractionRegistered, this.wasPlaying);
205
+ if (debug) console.log("visible", this.enabled, this.playOnAwake, !this.isPlaying, AudioSource._userInteractionRegistered, this.wasPlaying);
206
206
  if (this.enabled && this.playOnAwake && !this.isPlaying && AudioSource._userInteractionRegistered && this.wasPlaying) {
207
207
  this.play();
208
208
  }
@@ -9,6 +9,7 @@ import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instan
9
9
  import { ConstructorConcrete, SourceIdentifier, IComponent, IGameObject, Constructor, GuidsMap, UIDProvider, Collision, ICollider } from "../engine/engine_types";
10
10
  import { addNewComponent, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, moveComponentInstance, removeComponent } from "../engine/engine_components";
11
11
  import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive, isDestroyed } from "../engine/engine_gameobject";
12
+ import { resolveUrl } from "../engine/engine_utils";
12
13
 
13
14
 
14
15
  // export interface ISerializationCallbackReceiver {
@@ -641,7 +642,6 @@ class Component implements IComponent, EventTarget {
641
642
 
642
643
  return false;
643
644
  }
644
-
645
645
  }
646
646
 
647
647
  class Behaviour extends Component {