@needle-tools/engine 3.5.3-alpha.1 → 3.5.5-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 (59) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/needle-engine.js +8168 -8135
  3. package/dist/needle-engine.min.js +310 -310
  4. package/dist/needle-engine.umd.cjs +296 -296
  5. package/lib/engine/codegen/register_types.js +2 -2
  6. package/lib/engine/codegen/register_types.js.map +1 -1
  7. package/lib/engine/engine_addressables.js +3 -0
  8. package/lib/engine/engine_addressables.js.map +1 -1
  9. package/lib/engine/engine_constants.d.ts +2 -0
  10. package/lib/engine/engine_constants.js +2 -0
  11. package/lib/engine/engine_constants.js.map +1 -1
  12. package/lib/engine/engine_context.d.ts +2 -0
  13. package/lib/engine/engine_context.js +5 -0
  14. package/lib/engine/engine_context.js.map +1 -1
  15. package/lib/engine/engine_license.js +1 -1
  16. package/lib/engine/engine_license.js.map +1 -1
  17. package/lib/engine/engine_serialization_core.js +1 -1
  18. package/lib/engine/engine_serialization_core.js.map +1 -1
  19. package/lib/engine/engine_three_utils.d.ts +5 -0
  20. package/lib/engine/engine_three_utils.js +29 -12
  21. package/lib/engine/engine_three_utils.js.map +1 -1
  22. package/lib/engine/engine_utils.d.ts +12 -0
  23. package/lib/engine/engine_utils.js +20 -0
  24. package/lib/engine/engine_utils.js.map +1 -1
  25. package/lib/engine-components/AnimatorController.js +1 -2
  26. package/lib/engine-components/AnimatorController.js.map +1 -1
  27. package/lib/engine-components/codegen/components.d.ts +1 -1
  28. package/lib/engine-components/codegen/components.js +1 -1
  29. package/lib/engine-components/codegen/components.js.map +1 -1
  30. package/lib/engine-components/export/usdz/USDZExporter.d.ts +2 -2
  31. package/lib/engine-components/export/usdz/USDZExporter.js +23 -11
  32. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  33. package/lib/engine-components/ui/Graphic.js +3 -2
  34. package/lib/engine-components/ui/Graphic.js.map +1 -1
  35. package/lib/engine-components/ui/Text.js +5 -2
  36. package/lib/engine-components/ui/Text.js.map +1 -1
  37. package/lib/needle-engine.js +5 -0
  38. package/lib/needle-engine.js.map +1 -1
  39. package/lib/tsconfig.tsbuildinfo +1 -1
  40. package/package.json +1 -1
  41. package/plugins/vite/copyfiles.js +49 -2
  42. package/plugins/vite/defines.js +12 -6
  43. package/plugins/vite/license.js +16 -3
  44. package/plugins/vite/meta.js +16 -3
  45. package/plugins/vite/utils.js +11 -0
  46. package/src/engine/codegen/register_types.js +2 -2
  47. package/src/engine/engine_addressables.ts +2 -1
  48. package/src/engine/engine_constants.ts +3 -0
  49. package/src/engine/engine_context.ts +6 -0
  50. package/src/engine/engine_license.ts +1 -1
  51. package/src/engine/engine_serialization_core.ts +1 -1
  52. package/src/engine/engine_three_utils.ts +28 -12
  53. package/src/engine/engine_utils.ts +34 -0
  54. package/src/engine-components/AnimatorController.ts +1 -1
  55. package/src/engine-components/codegen/components.ts +1 -1
  56. package/src/engine-components/export/usdz/USDZExporter.ts +19 -7
  57. package/src/engine-components/ui/Graphic.ts +3 -1
  58. package/src/engine-components/ui/Text.ts +5 -4
  59. package/src/needle-engine.ts +10 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "3.5.3-alpha.1",
3
+ "version": "3.5.5-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,5 @@
1
1
 
2
- import { resolve, join } from 'path'
2
+ import { resolve, join, isAbsolute } from 'path'
3
3
  import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir } from 'fs';
4
4
  import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js';
5
5
 
@@ -51,6 +51,30 @@ async function run(isBuild, config) {
51
51
  if (!existsSync(outDir)) {
52
52
  mkdirSync(outDir);
53
53
  }
54
+
55
+ // copy a list of files or directories declared in build.copy = [] in the needle.config.json
56
+ /*
57
+ "build": {
58
+ "copy": ["myFolder", "myFile.txt"]
59
+ }
60
+ */
61
+ if (needleConfig?.build?.copy) {
62
+ const arr = needleConfig.build.copy;
63
+ for (let i = 0; i < arr.length; i++) {
64
+ const entry = arr[i];
65
+ if (Array.isArray(entry)) {
66
+ console.log("WARN: build.copy can only contain string paths to copy to. Found array instead.");
67
+ continue;
68
+ }
69
+ const src = resolve(baseDir, entry);
70
+ const dest = resolvePath(outDir, entry);
71
+ if (existsSync(src)) {
72
+ console.log(`[${pluginName}] - Copy ${entry} to ${outdirName}/${entry}`)
73
+ copyRecursiveSync(src, dest);
74
+ }
75
+ }
76
+ }
77
+
54
78
  // copy assets dir
55
79
  const assetsDir = resolve(baseDir, assetsDirName);
56
80
  if (existsSync(assetsDir)) {
@@ -68,13 +92,36 @@ async function run(isBuild, config) {
68
92
  }
69
93
  }
70
94
 
95
+ /** resolves relative or absolute paths to a path inside the out directory
96
+ * for example D:/myFile.txt would resolve to outDir/myFile.txt
97
+ * wherereas "some/relative/path" would become outDir/some/relative/path
98
+ */
99
+ function resolvePath(outDir, pathValue) {
100
+ if (isAbsolute(pathValue)) {
101
+ var exists = existsSync(pathValue);
102
+ if (!exists) return null;
103
+ var stats = exists && statSync(pathValue);
104
+ if (stats.isDirectory()) {
105
+ const dirName = pathValue.replaceAll('\\', '/').split('/').pop();
106
+ return resolve(outDir, dirName);
107
+ }
108
+ const fileName = pathValue.replaceAll('\\', '/').split('/').pop();
109
+ return resolve(outDir, fileName);
110
+ }
111
+ return resolve(outDir, pathValue);
112
+ }
113
+
71
114
  function copyRecursiveSync(src, dest) {
115
+ if (dest === null) {
116
+ console.log(`[${pluginName}] - Copy ${src} to ${dest} - dest is null`)
117
+ return;
118
+ }
72
119
  var exists = existsSync(src);
73
120
  var stats = exists && statSync(src);
74
121
  var isDirectory = exists && stats.isDirectory();
75
122
  if (isDirectory) {
76
123
  if (!existsSync(dest))
77
- mkdirSync(dest);
124
+ mkdirSync(dest, { recursive: true });
78
125
  readdirSync(src).forEach(function (childItemName) {
79
126
  copyRecursiveSync(join(src, childItemName), join(dest, childItemName));
80
127
  });
@@ -1,19 +1,26 @@
1
1
  import { loadConfig } from "./config.js";
2
+ import { tryGetNeedleEngineVersion } from "./utils.js";
2
3
 
3
4
  /** used to pass config variables into vite.config.define
4
5
  * for example "useRapier"
5
6
  */
6
- export const needleDefines = (command, config, userSettings) => {
7
+ export const needleDefines = (command, needleEngineConfig, userSettings) => {
7
8
 
8
9
  if (!userSettings) userSettings = {};
9
10
 
10
11
  let useRapier = true;
11
- if (config.useRapier === false || userSettings?.useRapier === false) useRapier = false;
12
+ if (needleEngineConfig.useRapier === false || userSettings?.useRapier === false) useRapier = false;
12
13
 
13
14
  return {
14
15
  name: 'needle-defines',
15
16
  enforce: 'pre',
16
- config(config) {
17
+ config(viteConfig) {
18
+ if (!viteConfig.define) viteConfig.define = {};
19
+ viteConfig.define.NEEDLE_ENGINE_META = {
20
+ version: tryGetNeedleEngineVersion(),
21
+ generator: needleEngineConfig.generator,
22
+ }
23
+
17
24
  if (useRapier && userSettings?.useRapier !== true) {
18
25
  const meta = loadConfig();
19
26
  if (meta?.useRapier === false) {
@@ -21,9 +28,8 @@ export const needleDefines = (command, config, userSettings) => {
21
28
  }
22
29
  }
23
30
  console.log("UseRapier?", useRapier);
24
- if (!config.define) config.define = {};
25
- if (config.define.NEEDLE_USE_RAPIER === undefined) {
26
- config.define.NEEDLE_USE_RAPIER = useRapier;
31
+ if (viteConfig.define.NEEDLE_USE_RAPIER === undefined) {
32
+ viteConfig.define.NEEDLE_USE_RAPIER = useRapier;
27
33
  }
28
34
  }
29
35
  }
@@ -1,5 +1,6 @@
1
1
  import { loadConfig } from './config.js';
2
2
 
3
+ let didLog = false;
3
4
 
4
5
  export const needleLicense = (command, config, userSettings) => {
5
6
 
@@ -7,15 +8,27 @@ export const needleLicense = (command, config, userSettings) => {
7
8
  name: "needle-license",
8
9
  enforce: 'pre',
9
10
  async transform(src, id) {
10
- const isNeedleEngineFile = id.includes("engine/engine_license") || id.includes("needle-tools_engine.js");
11
+ const isNeedleEngineFile = id.includes("engine/engine_license") || id.includes("needle-tools_engine");
11
12
  // sometimes the actual license parameter is in a unnamed chunk file
12
13
  const isViteChunkFile = id.includes("chunk") && id.includes(".vite");
13
14
  if (isNeedleEngineFile || isViteChunkFile) {
14
15
  const needleConfig = await loadConfig();
15
16
  if (needleConfig) {
16
17
  if (typeof needleConfig.license === "string") {
17
- src = src.replace("const NEEDLE_ENGINE_LICENSE_TYPE: string = \"\";", "const NEEDLE_ENGINE_LICENSE_TYPE: string = \"" + needleConfig.license + "\";");
18
- return { code: src, map: null }
18
+ if (!didLog) {
19
+ didLog = true;
20
+ console.log("Applying license: " + needleConfig.license);
21
+ }
22
+ const index = src.indexOf("NEEDLE_ENGINE_LICENSE_TYPE");
23
+ if (index >= 0) {
24
+ const end = src.indexOf(";", index);
25
+ if (end >= 0) {
26
+ const line = src.substring(index, end);
27
+ const replaced = "NEEDLE_ENGINE_LICENSE_TYPE = \"" + needleConfig.license + "\"";
28
+ src = src.replace(line, replaced);
29
+ return { code: src, map: null }
30
+ }
31
+ }
19
32
  }
20
33
  }
21
34
  else {
@@ -1,6 +1,7 @@
1
1
  import { loadConfig } from './config.js';
2
2
  import fs from 'fs';
3
3
  import { getPosterPath } from './poster.js';
4
+ import { tryGetNeedleEngineVersion } from './utils.js';
4
5
 
5
6
  export const needleMeta = (command, config, userSettings) => {
6
7
 
@@ -15,7 +16,7 @@ export const needleMeta = (command, config, userSettings) => {
15
16
 
16
17
  return {
17
18
  // replace meta tags
18
- name: 'needle-meta-tags',
19
+ name: 'needle-meta',
19
20
  transformIndexHtml: {
20
21
  enforce: 'pre',
21
22
  transform(html, _ctx) {
@@ -91,8 +92,19 @@ export const needleMeta = (command, config, userSettings) => {
91
92
  }
92
93
  }
93
94
 
94
- // if(!tags.filter(t => t.attrs?.name === "generator"))
95
- tags.push({ tag: 'meta', attrs: { name: 'generator', content: 'Needle' } });
95
+ let generator = "Needle";
96
+ if (config.generator?.length > 5) {
97
+ generator = config.generator;
98
+ }
99
+ tags.push({ tag: 'meta', attrs: { name: 'generator', content: generator } });
100
+
101
+ const needleEngineVersion = tryGetNeedleEngineVersion();
102
+ if (needleEngineVersion) {
103
+ if (command === "build")
104
+ console.log("Needle Engine version: " + needleEngineVersion);
105
+ tags.push({ tag: 'meta', attrs: { name: 'needle-engine', content: needleEngineVersion } });
106
+ }
107
+ else console.log("WARN: could not find needle engine package.json")
96
108
 
97
109
  return { html, tags }
98
110
  },
@@ -100,6 +112,7 @@ export const needleMeta = (command, config, userSettings) => {
100
112
  }
101
113
  }
102
114
 
115
+
103
116
  function updateUrlMetaTag(html, url) {
104
117
  html = html.replace(`<meta name="url" content="http://needle.tools">`, `<meta name="url" content="${url}">`);
105
118
  return html;
@@ -0,0 +1,11 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+
3
+ export function tryGetNeedleEngineVersion() {
4
+ const needleEnginePackageJsonPath = process.cwd() + "/node_modules/@needle-tools/engine/package.json";
5
+ if (existsSync(needleEnginePackageJsonPath)) {
6
+ const json = JSON.parse(readFileSync(needleEnginePackageJsonPath));
7
+ const version = json.version;
8
+ return version;
9
+ }
10
+ return null;
11
+ }
@@ -54,6 +54,7 @@ import { ColorBySpeedModule } from "../../engine-components/ParticleSystemModule
54
54
  import { ColorOverLifetimeModule } from "../../engine-components/ParticleSystemModules";
55
55
  import { Component } from "../../engine-components/Component";
56
56
  import { ControlTrackHandler } from "../../engine-components/timeline/TimelineTracks";
57
+ import { CustomBranding } from "../../engine-components/export/usdz/USDZExporter";
57
58
  import { Deletable } from "../../engine-components/DeleteBox";
58
59
  import { DeleteBox } from "../../engine-components/DeleteBox";
59
60
  import { DepthOfField } from "../../engine-components/postprocessing/Effects/DepthOfField";
@@ -126,7 +127,6 @@ import { PostProcessingHandler } from "../../engine-components/postprocessing/Po
126
127
  import { PreliminaryAction } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
127
128
  import { PreliminaryTrigger } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
128
129
  import { PresentationMode } from "../../engine-components-experimental/Presentation";
129
- import { QuickLookOverlay } from "../../engine-components/export/usdz/USDZExporter";
130
130
  import { RawImage } from "../../engine-components/ui/Image";
131
131
  import { Raycaster } from "../../engine-components/ui/Raycaster";
132
132
  import { Rect } from "../../engine-components/ui/RectTransform";
@@ -270,6 +270,7 @@ TypeStore.add("ColorBySpeedModule", ColorBySpeedModule);
270
270
  TypeStore.add("ColorOverLifetimeModule", ColorOverLifetimeModule);
271
271
  TypeStore.add("Component", Component);
272
272
  TypeStore.add("ControlTrackHandler", ControlTrackHandler);
273
+ TypeStore.add("CustomBranding", CustomBranding);
273
274
  TypeStore.add("Deletable", Deletable);
274
275
  TypeStore.add("DeleteBox", DeleteBox);
275
276
  TypeStore.add("DepthOfField", DepthOfField);
@@ -342,7 +343,6 @@ TypeStore.add("PostProcessingHandler", PostProcessingHandler);
342
343
  TypeStore.add("PreliminaryAction", PreliminaryAction);
343
344
  TypeStore.add("PreliminaryTrigger", PreliminaryTrigger);
344
345
  TypeStore.add("PresentationMode", PresentationMode);
345
- TypeStore.add("QuickLookOverlay", QuickLookOverlay);
346
346
  TypeStore.add("RawImage", RawImage);
347
347
  TypeStore.add("Raycaster", Raycaster);
348
348
  TypeStore.add("Rect", Rect);
@@ -353,7 +353,7 @@ new AddressableSerializer();
353
353
 
354
354
 
355
355
 
356
-
356
+ const failedTexturePromise = Promise.resolve(null);
357
357
 
358
358
  export class ImageReference {
359
359
 
@@ -392,6 +392,7 @@ export class ImageReference {
392
392
 
393
393
  private loader: TextureLoader | null = null;
394
394
  createTexture(): Promise<Texture | null> {
395
+ if (!this.url) return failedTexturePromise;
395
396
  if (!this.loader) this.loader = new TextureLoader();
396
397
  this.loader.setCrossOrigin("anonymous");
397
398
  return this.loader.loadAsync(this.url);
@@ -1,3 +1,6 @@
1
+ declare const NEEDLE_ENGINE_META: { version: string, generator: string };
2
+ export const NEEDLE_ENGINE_VERSION = NEEDLE_ENGINE_META.version;
3
+ export const NEEDLE_ENGINE_GENERATOR = NEEDLE_ENGINE_META.generator;
1
4
 
2
5
  export const activeInHierarchyFieldName = "needle_isActiveInHierarchy";
3
6
  export const builtinComponentKeyName = "builtin_components";
@@ -29,6 +29,7 @@ import { CoroutineData, ICamera, IComponent, IContext, ILight } from "./engine_t
29
29
  import { destroy, foreachComponent } from './engine_gameobject';
30
30
  import { ContextEvent, ContextRegistry } from './engine_context_registry';
31
31
  import { delay } from './engine_utils';
32
+ import { NEEDLE_ENGINE_VERSION } from './engine_constants';
32
33
  // import { createCameraWithOrbitControl } from '../engine-components/CameraUtils';
33
34
 
34
35
 
@@ -93,6 +94,11 @@ export function registerComponent(script: IComponent, context?: Context) {
93
94
 
94
95
  export class Context implements IContext {
95
96
 
97
+ /** the needle engine version */
98
+ get version() {
99
+ return NEEDLE_ENGINE_VERSION;
100
+ }
101
+
96
102
  static get Current(): Context {
97
103
  return ContextRegistry.Current as Context;
98
104
  }
@@ -7,7 +7,7 @@ const debug = getParam("debuglicense");
7
7
 
8
8
  // This is modified by a bundler (e.g. vite)
9
9
  // Do not edit manually
10
- const NEEDLE_ENGINE_LICENSE_TYPE: string = "";
10
+ const NEEDLE_ENGINE_LICENSE_TYPE: string = "basic";
11
11
  if (debug) console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)
12
12
 
13
13
  export function hasProLicense() {
@@ -436,7 +436,7 @@ function implictlyAssignPrimitiveTypes(obj: any, serializedData: any) {
436
436
  if (isPrimitiveType(data[key]) && !isPrimitiveType(member)) {
437
437
 
438
438
  const prop = tryFindPropertyDescriptor(member, key);
439
- if (prop?.writable === false && (prop && prop.set === undefined)) {
439
+ if (prop && (prop?.writable === undefined || prop?.writable === false) && (prop.set === undefined)) {
440
440
  if (debug)
441
441
  console.warn("Property is not writable \"" + key + "\"", member, prop, data[key], member[key]);
442
442
  continue;
@@ -50,13 +50,13 @@ export function setWorldPositionXYZ(obj: Object3D, x: number, y: number, z: numb
50
50
  }
51
51
 
52
52
 
53
+ const _worldQuaternions = new CircularBuffer(() => new Quaternion(), 100);
53
54
  const _worldQuaternionBuffer: Quaternion = new Quaternion();
54
- const _worldQuaternion: Quaternion = new Quaternion();
55
55
  const _tempQuaternionBuffer2: Quaternion = new Quaternion();
56
56
 
57
57
  export function getWorldQuaternion(obj: Object3D, target: Quaternion | null = null): Quaternion {
58
- if (!obj) return _worldQuaternion.set(0, 0, 0, 1);
59
- const quat = target ?? _worldQuaternion;
58
+ if (!obj) return _worldQuaternions.get().identity();
59
+ const quat = target ?? _worldQuaternions.get();
60
60
  if (!obj.parent) return quat.copy(obj.quaternion);
61
61
  obj.getWorldQuaternion(quat);
62
62
  return quat;
@@ -79,14 +79,16 @@ export function setWorldQuaternionXYZW(obj: Object3D, x: number, y: number, z: n
79
79
  setWorldQuaternion(obj, _worldQuaternionBuffer);
80
80
  }
81
81
 
82
+ const _worldScaleBuffer = new CircularBuffer(() => new Vector3(), 100);
82
83
  const _worldScale: Vector3 = new Vector3();
83
- const _worldScale2: Vector3 = new Vector3();
84
84
 
85
85
  export function getWorldScale(obj: Object3D, vec: Vector3 | null = null): Vector3 {
86
- if (!obj) return _worldScale.set(0, 0, 0);
87
- if (!obj.parent) return _worldScale.copy(obj.scale);
88
- obj.getWorldScale(vec ?? _worldScale);
89
- return vec ?? _worldScale;
86
+ if (!vec)
87
+ vec = _worldScaleBuffer.get();
88
+ if (!obj) return vec.set(0, 0, 0);
89
+ if (!obj.parent) return vec.copy(obj.scale);
90
+ obj.getWorldScale(vec);
91
+ return vec;
90
92
  }
91
93
 
92
94
  export function setWorldScale(obj: Object3D, vec: Vector3) {
@@ -95,7 +97,7 @@ export function setWorldScale(obj: Object3D, vec: Vector3) {
95
97
  obj.scale.copy(vec);
96
98
  return;
97
99
  }
98
- const tempVec = _worldScale2;
100
+ const tempVec = _worldScale;
99
101
  const obj2 = obj.parent;
100
102
  obj2.getWorldScale(tempVec);
101
103
  obj.scale.copy(vec);
@@ -109,6 +111,18 @@ export function forward(obj: Object3D): Vector3 {
109
111
  return _forward.set(0, 0, 1).applyQuaternion(_forwardQuat);
110
112
  }
111
113
 
114
+ const _worldDirectionBuffer = new CircularBuffer(() => new Vector3(), 100);
115
+ const _worldDirectionQuat = new Quaternion();
116
+ /** Get the world direction. Returns world forward if nothing is passed in.
117
+ * Pass in a relative direction to get it converted to world space (e.g. dir = new Vector3(0, 1, 1))
118
+ * The returned vector will not be normalized
119
+ */
120
+ export function getWorldDirection(obj: Object3D, dir?: Vector3) {
121
+ // If no direction is passed in set the direction to the forward vector
122
+ if (!dir) dir = _worldDirectionBuffer.get().set(0, 0, 1);
123
+ getWorldQuaternion(obj, _worldDirectionQuat);
124
+ return dir.applyQuaternion(_worldDirectionQuat);
125
+ }
112
126
 
113
127
 
114
128
  const _worldEulerBuffer: Euler = new Euler();
@@ -119,14 +133,16 @@ const _worldRotation: Vector3 = new Vector3();
119
133
 
120
134
  // world euler (in radians)
121
135
  export function getWorldEuler(obj: Object3D): Euler {
122
- obj.getWorldQuaternion(_worldQuaternion);
123
- _worldEuler.setFromQuaternion(_worldQuaternion);
136
+ const quat = _worldQuaternions.get();
137
+ obj.getWorldQuaternion(quat);
138
+ _worldEuler.setFromQuaternion(quat);
124
139
  return _worldEuler;
125
140
  }
126
141
 
127
142
  // world euler (in radians)
128
143
  export function setWorldEuler(obj: Object3D, val: Euler) {
129
- setWorldQuaternion(obj, _worldQuaternion.setFromEuler(val));;
144
+ const quat = _worldQuaternions.get();
145
+ setWorldQuaternion(obj, quat.setFromEuler(val));;
130
146
  }
131
147
 
132
148
  // returns rotation in degrees
@@ -408,4 +408,38 @@ export function isSafari() {
408
408
 
409
409
  export function isQuest() {
410
410
  return navigator.userAgent.includes("OculusBrowser");
411
+ }
412
+
413
+
414
+
415
+ const cloudflareIPRegex = /ip=(?<ip>.+?)\n/s;
416
+ export async function getIpCloudflare() {
417
+ const data = await fetch('https://www.cloudflare.com/cdn-cgi/trace');
418
+ const body = await data.text();
419
+ // we are only interested in the ip= part:
420
+ const match = cloudflareIPRegex.exec(body);
421
+ if (match)
422
+ return match[1];
423
+ return null;
424
+ }
425
+
426
+ export async function getIp() {
427
+ const res = await fetch("https://api.db-ip.com/v2/free/self");
428
+ const json = await res.json();
429
+ return json.ipAddress;
430
+ }
431
+
432
+ export type IpAndLocation = {
433
+ ipAddress: string;
434
+ continentCode: string;
435
+ continentName: string;
436
+ countryCode: string;
437
+ countryName: string;
438
+ stateProv: string;
439
+ city: string;
440
+ }
441
+ export async function getIpAndLocation(): Promise<IpAndLocation> {
442
+ const res = (await fetch("https://api.db-ip.com/v2/free/self").catch(() => null))!;
443
+ const json = await res.json() as IpAndLocation;
444
+ return json;
411
445
  }
@@ -408,7 +408,7 @@ export class AnimatorController {
408
408
  }
409
409
  }
410
410
  }
411
- else if (isDevEnvironment()) {
411
+ else if (debug) {
412
412
  if (!state["__warned_no_motion"]) {
413
413
  state["__warned_no_motion"] = true;
414
414
  console.warn("No action", state.motion, this);
@@ -52,6 +52,7 @@ export { ColorBySpeedModule } from "../ParticleSystemModules";
52
52
  export { ColorOverLifetimeModule } from "../ParticleSystemModules";
53
53
  export { Component } from "../Component";
54
54
  export { ControlTrackHandler } from "../timeline/TimelineTracks";
55
+ export { CustomBranding } from "../export/usdz/USDZExporter";
55
56
  export { Deletable } from "../DeleteBox";
56
57
  export { DeleteBox } from "../DeleteBox";
57
58
  export { DepthOfField } from "../postprocessing/Effects/DepthOfField";
@@ -121,7 +122,6 @@ export { PointerEventData } from "../ui/PointerEvents";
121
122
  export { PostProcessingHandler } from "../postprocessing/PostProcessingHandler";
122
123
  export { PreliminaryAction } from "../export/usdz/extensions/behavior/BehaviourComponents";
123
124
  export { PreliminaryTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents";
124
- export { QuickLookOverlay } from "../export/usdz/USDZExporter";
125
125
  export { RawImage } from "../ui/Image";
126
126
  export { Raycaster } from "../ui/Raycaster";
127
127
  export { Rect } from "../ui/RectTransform";
@@ -18,7 +18,7 @@ import { AudioExtension } from "./extensions/behavior/AudioExtension";
18
18
 
19
19
  const debug = getParam("debugusdz");
20
20
 
21
- export class QuickLookOverlay {
21
+ export class CustomBranding {
22
22
  @serializable()
23
23
  callToAction?: string;
24
24
  @serializable()
@@ -45,8 +45,8 @@ export class USDZExporter extends Behaviour {
45
45
  @serializable(URL)
46
46
  customUsdzFile?: string;
47
47
 
48
- @serializable(QuickLookOverlay)
49
- overlay?: QuickLookOverlay;
48
+ @serializable(CustomBranding)
49
+ customBranding?: CustomBranding;
50
50
 
51
51
  // Currently not exposed to integrations - not fully tested. Set from code (e.g. image tracking)
52
52
  @serializable()
@@ -66,6 +66,7 @@ export class USDZExporter extends Behaviour {
66
66
  private webARSessionRoot: WebARSessionRoot | undefined;
67
67
 
68
68
  start() {
69
+ console.log(this.customUsdzFile);
69
70
  if (debug) {
70
71
  console.log(this);
71
72
  console.log("Debug USDZ, press 't' to export")
@@ -249,16 +250,27 @@ export class USDZExporter extends Behaviour {
249
250
  if (debug)
250
251
  showBalloonMessage("Quicklook url: " + callToActionURL);
251
252
  if (callToActionURL) {
252
- globalThis.open(callToActionURL, "_blank");
253
+ if (!hasProLicense()) {
254
+ console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
255
+ }
256
+ else {
257
+ globalThis.open(callToActionURL, "_blank");
258
+ }
253
259
  }
254
260
  }
255
261
  }
256
262
  }
257
263
  }
258
264
 
259
- private buildQuicklookOverlay(): QuickLookOverlay {
260
- const obj: QuickLookOverlay = {};
261
- if (this.overlay) Object.assign(obj, this.overlay);
265
+ private buildQuicklookOverlay(): CustomBranding {
266
+ const obj: CustomBranding = {};
267
+ if (this.customBranding) Object.assign(obj, this.customBranding);
268
+ if (!hasProLicense()) {
269
+ console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
270
+ obj.callToAction = "Close";
271
+ obj.checkoutTitle = "🌵 Made with Needle";
272
+ obj.checkoutSubtitle = "_";
273
+ }
262
274
  if (!obj.callToAction?.length)
263
275
  obj.callToAction = "Close";
264
276
  if (!obj.checkoutTitle?.length)
@@ -174,7 +174,6 @@ export class Graphic extends BaseUIComponent implements IGraphic, IRectTransform
174
174
  static textureCache: Map<Texture, Texture> = new Map();
175
175
 
176
176
  protected async setTexture(tex: Texture | null | undefined) {
177
- if (!tex) return;
178
177
  this.setOptions({ backgroundOpacity: 0 });
179
178
  if (tex) {
180
179
  // workaround for https://github.com/needle-tools/needle-engine-support/issues/109
@@ -190,6 +189,9 @@ export class Graphic extends BaseUIComponent implements IGraphic, IRectTransform
190
189
  }
191
190
  this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
192
191
  }
192
+ else {
193
+ this.setOptions({ backgroundImage: null, borderRadius: 0, backgroundOpacity: this.color.alpha });
194
+ }
193
195
  }
194
196
 
195
197
  protected onAfterAddedToScene(): void {
@@ -61,10 +61,11 @@ export class Text extends Graphic {
61
61
  }
62
62
 
63
63
  set text(val: string) {
64
-
65
-
66
- this._text = val;
67
- this.feedText(this.text, this.supportRichText);
64
+ if (val !== this._text) {
65
+ this._text = val;
66
+ this.feedText(this.text, this.supportRichText);
67
+ this.markDirty();
68
+ }
68
69
  }
69
70
 
70
71
  private set_text(val: string) {
@@ -1,6 +1,8 @@
1
1
  import { makeErrorsVisibleForDevelopment } from "./engine/debug/debug_overlay";
2
2
  makeErrorsVisibleForDevelopment();
3
3
 
4
+ import { NEEDLE_ENGINE_GENERATOR, NEEDLE_ENGINE_VERSION } from "./engine/engine_constants";
5
+
4
6
  import "./engine/engine_element";
5
7
  import "./engine/engine_setup";
6
8
  import "./engine-components/CameraUtils"
@@ -9,6 +11,7 @@ export * from "./engine/api";
9
11
  export * from "./engine-components/api";
10
12
  export * from "./engine-components-experimental/api";
11
13
 
14
+
12
15
  // make accessible for external javascript
13
16
  import { Context } from "./engine/engine_setup";
14
17
  const Needle = { Context: Context };
@@ -27,6 +30,12 @@ registerGlobal(Component);
27
30
  import * as Components from "./engine-components/codegen/components";
28
31
  registerGlobal(Components);
29
32
 
33
+ Needle["$meta"] = {
34
+ version: NEEDLE_ENGINE_VERSION,
35
+ generator: NEEDLE_ENGINE_GENERATOR
36
+ };
37
+
38
+
30
39
  import { GameObject } from "./engine-components/Component";
31
40
  for (const method of Object.getOwnPropertyNames(GameObject)) {
32
41
  switch (method) {
@@ -50,4 +59,4 @@ else console.warn("Threejs is already imported");
50
59
 
51
60
 
52
61
 
53
- import "./engine/engine_license";
62
+ import "./engine/engine_license";